From 058da7b23204f079346b6bb93f917abb86a93c4b Mon Sep 17 00:00:00 2001 From: gabito1451 Date: Tue, 24 Mar 2026 11:09:09 -0700 Subject: [PATCH 1/3] Implement automated database backup process --- Dockerfile | 6 +- scripts/backup.sh | 9 ++ scripts/test-restore.sh | 115 ++++++++++++++++++ src/app.module.ts | 2 + .../backup-verification.service.ts | 48 ++++++++ .../database-backup.service.ts | 9 ++ 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 scripts/test-restore.sh diff --git a/Dockerfile b/Dockerfile index 1a978182..b9710139 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat postgresql-client bash WORKDIR /app # Install dependencies based on the preferred package manager @@ -34,6 +34,10 @@ COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nestjs:nodejs /app/package.json ./package.json +# Copy scripts for backups +COPY --from=builder --chown=nestjs:nodejs /app/scripts ./scripts +RUN chmod +x scripts/*.sh + # Create logs directory RUN mkdir -p logs && chown nestjs:nodejs logs diff --git a/scripts/backup.sh b/scripts/backup.sh index d9020854..567e997a 100644 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -83,6 +83,15 @@ pg_dump \ echo "Calculating checksum..." sha256sum "$BACKUP_DIR/$BACKUP_NAME.dump" > "$BACKUP_DIR/$BACKUP_NAME.sha256" +# Validate backup +echo "Validating backup..." +if pg_restore -l "$BACKUP_DIR/$BACKUP_NAME.dump" > /dev/null 2>&1; then + echo "Backup validation passed: Dump is readable." +else + echo "ERROR: Backup validation failed: Dump is corrupted or unreadable!" + exit 1 +fi + # Compress backup echo "Compressing backup..." gzip -k "$BACKUP_DIR/$BACKUP_NAME.dump" diff --git a/scripts/test-restore.sh b/scripts/test-restore.sh new file mode 100644 index 00000000..a13b3c4b --- /dev/null +++ b/scripts/test-restore.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# PropChain Database Restore Testing Script +# Usage: ./test-restore.sh + +set -e + +# Configuration +DATABASE_URL="${DATABASE_URL:-postgresql://postgres:password@localhost:5432/propchain}" +TEST_DB_NAME="propchain_test_restore_$(date +%s)" + +# Parse DATABASE_URL +parse_db_url() { + local url="$1" + # Remove protocol + url="${url#postgresql://}" + + # Extract user:password + local auth="${url%%@*}" + DB_USER="${auth%%:*}" + DB_PASSWORD="${auth#*:}" + + # Extract host:port/database + url="${url#*@}" + local host_port="${url%%/*}" + DB_HOST="${host_port%%:*}" + DB_PORT="${host_port#*:}" +} + +# Check arguments +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +BACKUP_FILE="$1" + +# Check if backup file exists +if [ ! -f "$BACKUP_FILE" ]; then + echo "Error: Backup file not found: $BACKUP_FILE" + exit 1 +fi + +# Parse database URL +parse_db_url "$DATABASE_URL" + +echo "=== PropChain Backup Restoration Test ===" +echo "Backup File: $BACKUP_FILE" +echo "Test Database: $TEST_DB_NAME" +echo "" + +# Set password for psql/pg_restore +export PGPASSWORD="$DB_PASSWORD" + +# Decompress if necessary +RESTORE_FILE="$BACKUP_FILE" +TEMP_FILE="" +if [[ "$BACKUP_FILE" == *.gz ]]; then + echo "Decompressing backup..." + RESTORE_FILE="$(mktemp --suffix=.dump)" + TEMP_FILE="$RESTORE_FILE" + gunzip -c "$BACKUP_FILE" > "$RESTORE_FILE" +fi + +# Create test database +echo "Creating test database $TEST_DB_NAME..." +psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE $TEST_DB_NAME;" + +# Function to clean up +cleanup() { + echo "Cleaning up..." + psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS $TEST_DB_NAME;" + if [ -n "$TEMP_FILE" ] && [ -f "$TEMP_FILE" ]; then + rm "$TEMP_FILE" + fi +} + +trap cleanup EXIT + +# Restore backup to test database +echo "Restoring backup to test database..." +pg_restore \ + -h "$DB_HOST" \ + -p "$DB_PORT" \ + -U "$DB_USER" \ + -d "$TEST_DB_NAME" \ + --no-owner \ + --no-privileges \ + "$RESTORE_FILE" > /dev/null 2>&1 + +# Run verification checks +echo "Running verification checks..." + +# Check 1: Can we connect? +psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" -c "SELECT 1;" > /dev/null + +# Check 2: Check for essential tables (e.g., users) +TABLE_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';") +echo "Found $TABLE_COUNT tables in restored database." + +if [ "$TABLE_COUNT" -lt 5 ]; then + echo "ERROR: Too few tables found ($TABLE_COUNT). Restore might have failed." + exit 1 +fi + +# Check 3: Check data in key tables +# We use -t to get only the value +USER_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" -t -c "SELECT count(*) FROM \"users\";" | xargs) +PROPERTY_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$TEST_DB_NAME" -t -c "SELECT count(*) FROM \"properties\";" | xargs) + +echo "Restored User Count: $USER_COUNT" +echo "Restored Property Count: $PROPERTY_COUNT" + +echo "" +echo "=== Restoration Test Successful ===" diff --git a/src/app.module.ts b/src/app.module.ts index 895dfc0b..8e219a0f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -41,6 +41,7 @@ import { ValuationModule } from './valuation/valuation.module'; import { ApiKeysModule } from './api-keys/api-keys.module'; import { DocumentsModule } from './documents/documents.module'; import { SecurityModule } from './security/security.module'; +import { BackupRecoveryModule } from './backup-recovery/backup-recovery.module'; // Compliance & Security Modules import { AuditModule } from './common/audit/audit.module'; @@ -126,6 +127,7 @@ import { ObservabilityModule } from './observability/observability.module'; AuditModule, RbacModule, ObservabilityModule, + BackupRecoveryModule, ], controllers: [ AuditController, // Add the audit controller diff --git a/src/backup-recovery/backup-verification.service.ts b/src/backup-recovery/backup-verification.service.ts index 8200eda2..279ccb72 100644 --- a/src/backup-recovery/backup-verification.service.ts +++ b/src/backup-recovery/backup-verification.service.ts @@ -111,6 +111,11 @@ export class BackupVerificationService { // Verify content structure await this.verifyBackupStructure(backupPath, check); + // Full restoration test (automated testing) + if (check.accessible && check.tableIntegrity.errors.length === 0) { + await this.verifyRestoration(backupPath, check); + } + // Check restorability check.restorable = check.tableIntegrity.errors.length === 0 && check.accessible; @@ -212,6 +217,49 @@ export class BackupVerificationService { } } + /** + * Perform full restoration test + */ + private async verifyRestoration(filePath: string, check: BackupIntegrityCheck): Promise { + if (!filePath.endsWith('.dump') && !filePath.endsWith('.dump.gz')) { + return; // Only test restoration for postgres dumps + } + + this.logger.log(`Starting restoration test for ${filePath}`); + const execAsync = promisify(exec); + const scriptPath = path.join(process.cwd(), 'scripts', 'test-restore.sh'); + + try { + // Check if script exists and is executable + if (!fsSync.existsSync(scriptPath)) { + this.logger.warn(`Restoration test script not found at ${scriptPath}`); + return; + } + + await fs.chmod(scriptPath, 0o755); + + const databaseUrl = this.configService.get('DATABASE_URL'); + const { stdout } = await execAsync(`bash "${scriptPath}" "${filePath}"`, { + env: { ...process.env, DATABASE_URL: databaseUrl }, + }); + + this.logger.log(`Restoration test output: ${stdout}`); + + // Look for User and Property counts in output + const userCountMatch = stdout.match(/Restored User Count: (\d+)/); + const propertyCountMatch = stdout.match(/Restored Property Count: (\d+)/); + + if (userCountMatch && propertyCountMatch) { + this.logger.log(`Restoration test PASSED: Users=${userCountMatch[1]}, Properties=${propertyCountMatch[1]}`); + } else { + this.logger.warn('Restoration test completed but count regex failed'); + } + } catch (error) { + this.logger.error(`Restoration test failed: ${error.message}`); + check.tableIntegrity.errors.push(`Restoration test failed: ${error.message}`); + } + } + /** * Verify tar archive backup */ diff --git a/src/backup-recovery/database-backup.service.ts b/src/backup-recovery/database-backup.service.ts index 64c44024..b11670f0 100644 --- a/src/backup-recovery/database-backup.service.ts +++ b/src/backup-recovery/database-backup.service.ts @@ -16,6 +16,7 @@ import { BackupConfiguration, PointInTimeRecovery, } from './backup.types'; +import { BackupVerificationService } from './backup-verification.service'; const execAsync = promisify(exec); @@ -34,6 +35,7 @@ export class DatabaseBackupService { constructor( private readonly prisma: PrismaService, private readonly configService: ConfigService, + private readonly verificationService: BackupVerificationService, ) { this.backupDir = path.join(process.cwd(), this.configService.get('BACKUP_DIR') || 'backups/database'); this.initializeBackupDirectory(); @@ -142,6 +144,13 @@ export class DatabaseBackupService { this.logger.log(`Full backup completed: ${backupId} (${metadata.size} bytes in ${metadata.duration}ms)`); + // Trigger immediate verification + try { + await this.verificationService.verifyBackup(backupId); + } catch (verifyError) { + this.logger.warn(`Immediate verification failed for backup ${backupId}: ${verifyError.message}`); + } + return metadata; } catch (error) { this.logger.error(`Full backup failed: ${error.message}`, error.stack); From e757082858ce8bb935bd1406046b2ecbd745476a Mon Sep 17 00:00:00 2001 From: gabito1451 Date: Tue, 24 Mar 2026 11:22:33 -0700 Subject: [PATCH 2/3] fix --- src/auth/auth.controller.ts | 22 ++++++---- src/auth/auth.service.ts | 18 ++++---- .../backup-verification.service.ts | 6 +-- src/communication/email/email.queue.ts | 41 ++++++++++--------- .../services/input-sanitization.service.ts | 1 - src/users/user.service.ts | 6 ++- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 00853275..5f0bb63b 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -102,10 +102,13 @@ export class AuthController { @ApiStandardErrorResponse([400, 401]) @HttpCode(HttpStatus.OK) async login(@Body() loginDto: LoginDto, @Req() req: Request) { - return this.authService.login({ - email: loginDto.email, - password: loginDto.password, - }, this.getRequestMeta(req)); + return this.authService.login( + { + email: loginDto.email, + password: loginDto.password, + }, + this.getRequestMeta(req), + ); } /** @@ -137,10 +140,13 @@ export class AuthController { @ApiStandardErrorResponse([401]) @HttpCode(HttpStatus.OK) async web3Login(@Body() loginDto: LoginWeb3Dto, @Req() req: Request) { - return this.authService.login({ - walletAddress: loginDto.walletAddress, - signature: loginDto.signature, - }, this.getRequestMeta(req)); + return this.authService.login( + { + walletAddress: loginDto.walletAddress, + signature: loginDto.signature, + }, + this.getRequestMeta(req), + ); } /** diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 71ce4f44..fa6b1067 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -362,7 +362,11 @@ export class AuthService { } const ttl = this.getSessionExpiry(session.lastActivity || session.createdAt); if (ttl > 0) { - await this.redisService.setex(tokenRevocationRedisKeys.accessRevoked(session.jti), Math.ceil(ttl / 1000), userId); + await this.redisService.setex( + tokenRevocationRedisKeys.accessRevoked(session.jti), + Math.ceil(ttl / 1000), + userId, + ); } } await this.redisService.del(tokenRevocationRedisKeys.activeSession(userId, sessionId)); @@ -474,11 +478,7 @@ export class AuthService { fingerprint: sessionMeta.fingerprint, }), ); - await this.redisService.setex( - tokenRevocationRedisKeys.userRefreshSession(user.id), - refreshTtl, - refreshSessionId, - ); + await this.redisService.setex(tokenRevocationRedisKeys.userRefreshSession(user.id), refreshTtl, refreshSessionId); const sessionExpiry = this.configService.get('SESSION_TIMEOUT', 3600); await this.redisService.setex(tokenRevocationRedisKeys.accessSession(jti), sessionExpiry, sessionId); @@ -563,7 +563,11 @@ export class AuthService { sessionExpiry, JSON.stringify(session), ); - await this.redisService.setex(tokenRevocationRedisKeys.accessSession(session.jti), sessionExpiry, session.sessionId); + await this.redisService.setex( + tokenRevocationRedisKeys.accessSession(session.jti), + sessionExpiry, + session.sessionId, + ); } private buildFingerprint(requestMeta?: { ip?: string; userAgent?: string }): string { diff --git a/src/backup-recovery/backup-verification.service.ts b/src/backup-recovery/backup-verification.service.ts index 279ccb72..2c0a0457 100644 --- a/src/backup-recovery/backup-verification.service.ts +++ b/src/backup-recovery/backup-verification.service.ts @@ -244,15 +244,15 @@ export class BackupVerificationService { }); this.logger.log(`Restoration test output: ${stdout}`); - + // Look for User and Property counts in output const userCountMatch = stdout.match(/Restored User Count: (\d+)/); const propertyCountMatch = stdout.match(/Restored Property Count: (\d+)/); if (userCountMatch && propertyCountMatch) { - this.logger.log(`Restoration test PASSED: Users=${userCountMatch[1]}, Properties=${propertyCountMatch[1]}`); + this.logger.log(`Restoration test PASSED: Users=${userCountMatch[1]}, Properties=${propertyCountMatch[1]}`); } else { - this.logger.warn('Restoration test completed but count regex failed'); + this.logger.warn('Restoration test completed but count regex failed'); } } catch (error) { this.logger.error(`Restoration test failed: ${error.message}`); diff --git a/src/communication/email/email.queue.ts b/src/communication/email/email.queue.ts index bcf8e6d1..1d395683 100644 --- a/src/communication/email/email.queue.ts +++ b/src/communication/email/email.queue.ts @@ -446,11 +446,7 @@ export class EmailQueueService implements OnModuleDestroy { * Set up queue event listeners */ private setupEventListeners(): void { - const registerListener = ( - queue: Bull.Queue, - event: string, - handler: (...args: any[]) => void, - ) => { + const registerListener = (queue: Bull.Queue, event: string, handler: (...args: any[]) => void) => { queue.on(event, handler); this.listenerDisposers.push(() => queue.removeListener(event, handler)); }; @@ -523,20 +519,23 @@ export class EmailQueueService implements OnModuleDestroy { } private startQueueCleanupMonitoring(): void { - this.queueCleanupMonitor = setInterval(async () => { - try { - await Promise.all([ - this.emailQueue.clean(24 * 60 * 60 * 1000, 'completed'), - this.emailQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), - this.priorityQueue.clean(24 * 60 * 60 * 1000, 'completed'), - this.priorityQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), - this.batchQueue.clean(24 * 60 * 60 * 1000, 'completed'), - this.batchQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), - ]); - } catch (error) { - this.logger.error('Failed to clean queue history', error); - } - }, this.configService.get('EMAIL_QUEUE_CLEANUP_INTERVAL_MS', 300000)); + this.queueCleanupMonitor = setInterval( + async () => { + try { + await Promise.all([ + this.emailQueue.clean(24 * 60 * 60 * 1000, 'completed'), + this.emailQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), + this.priorityQueue.clean(24 * 60 * 60 * 1000, 'completed'), + this.priorityQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), + this.batchQueue.clean(24 * 60 * 60 * 1000, 'completed'), + this.batchQueue.clean(7 * 24 * 60 * 60 * 1000, 'failed'), + ]); + } catch (error) { + this.logger.error('Failed to clean queue history', error); + } + }, + this.configService.get('EMAIL_QUEUE_CLEANUP_INTERVAL_MS', 300000), + ); this.queueCleanupMonitor.unref?.(); } @@ -573,7 +572,9 @@ export class EmailQueueService implements OnModuleDestroy { delete job.data.text; } } catch (error) { - this.logger.warn(`Failed to cleanup job resources for ${job?.id}: ${error instanceof Error ? error.message : error}`); + this.logger.warn( + `Failed to cleanup job resources for ${job?.id}: ${error instanceof Error ? error.message : error}`, + ); } } diff --git a/src/security/services/input-sanitization.service.ts b/src/security/services/input-sanitization.service.ts index 00c9b4ec..9c79bdde 100644 --- a/src/security/services/input-sanitization.service.ts +++ b/src/security/services/input-sanitization.service.ts @@ -76,7 +76,6 @@ export class InputSanitizationService { if (this.matchesPattern(value, SQL_INJECTION_PATTERNS)) { throw new BadRequestException(`Potential SQL injection detected in ${path}`); } - } private containsIllegalControlCharacters(value: string): boolean { diff --git a/src/users/user.service.ts b/src/users/user.service.ts index b0ca1194..9d6d30f2 100644 --- a/src/users/user.service.ts +++ b/src/users/user.service.ts @@ -486,7 +486,8 @@ export class UserService { return this.cacheService.wrap( `user:followers:${userId}:${limit}`, - () => this.monitorQuery('users.getFollowers', { userId, limit }, () => this.prisma.userRelationship.findMany(query)), + () => + this.monitorQuery('users.getFollowers', { userId, limit }, () => this.prisma.userRelationship.findMany(query)), { l1Ttl: 30, l2Ttl: 120, tags: ['user', `user:${userId}`] }, ); } @@ -520,7 +521,8 @@ export class UserService { return this.cacheService.wrap( `user:following:${userId}:${limit}`, - () => this.monitorQuery('users.getFollowing', { userId, limit }, () => this.prisma.userRelationship.findMany(query)), + () => + this.monitorQuery('users.getFollowing', { userId, limit }, () => this.prisma.userRelationship.findMany(query)), { l1Ttl: 30, l2Ttl: 120, tags: ['user', `user:${userId}`] }, ); } From 77108d3bf2ef433ec7daca0fcec08342f0693ece Mon Sep 17 00:00:00 2001 From: gabito1451 Date: Tue, 24 Mar 2026 11:37:55 -0700 Subject: [PATCH 3/3] fix --- build_output.txt | Bin 0 -> 33694 bytes build_output_2.txt | Bin 0 -> 17746 bytes src/models/api-key.entity.ts | 3 +++ src/models/user.entity.ts | 5 ----- src/users/user.controller.ts | 24 ++++++++---------------- 5 files changed, 11 insertions(+), 21 deletions(-) create mode 100644 build_output.txt create mode 100644 build_output_2.txt diff --git a/build_output.txt b/build_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff308feaa0fd639d22e8ffc8e7d00ed75426e85d GIT binary patch literal 33694 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 literal 0 HcmV?d00001 diff --git a/build_output_2.txt b/build_output_2.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69069a399f392a4850dae00e4fa0fa21f8fe664 GIT binary patch literal 17746 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 V1N4!IKhX4Z*-NUOZ)Lrk{{d;L=Z^pY literal 0 HcmV?d00001 diff --git a/src/models/api-key.entity.ts b/src/models/api-key.entity.ts index 8cfdf1b8..1fceba22 100644 --- a/src/models/api-key.entity.ts +++ b/src/models/api-key.entity.ts @@ -10,6 +10,9 @@ export class ApiKey implements PrismaApiKey { lastUsedAt: Date | null; isActive: boolean; rateLimit: number | null; + keyVersion: number; + lastRotatedAt: Date | null; + rotationDueAt: Date | null; createdAt: Date; updatedAt: Date; } diff --git a/src/models/user.entity.ts b/src/models/user.entity.ts index a79c8dcc..a4cdcae3 100644 --- a/src/models/user.entity.ts +++ b/src/models/user.entity.ts @@ -7,9 +7,6 @@ export class User implements PrismaUser { email: string; password: string | null; - firstName: string | null; - lastName: string | null; - walletAddress: string | null; isVerified: boolean; @@ -64,8 +61,6 @@ export class UserRelationship { export type CreateUserInput = { email: string; password?: string; - firstName?: string; - lastName?: string; walletAddress?: string; role?: UserRole; roleId?: string; diff --git a/src/users/user.controller.ts b/src/users/user.controller.ts index cdb503cf..4ad9feb5 100644 --- a/src/users/user.controller.ts +++ b/src/users/user.controller.ts @@ -1,13 +1,11 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; - import { - ApiResponse, ApiTags, ApiOperation, - ApiBody, + ApiResponse, + ApiBearerAuth, ApiParam, ApiExtraModels, ApiOkResponse, @@ -15,15 +13,13 @@ import { ApiNotFoundResponse, ApiConflictResponse, ApiCreatedResponse, - ApiBearerAuth, - ApiDeprecated, + ApiBody, ApiQuery, ApiConsumes, ApiProduces, ApiProperty, ApiPropertyOptional, ApiResponseOptions, - ApiVersion, } from '@nestjs/swagger'; import { UserResponseDto } from './dto/user-response.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @@ -42,15 +38,11 @@ export class UserController { @ApiCreatedResponse({ description: 'User created successfully.', type: UserResponseDto, - examples: { - success: { - value: { - id: 'user_abc123', - email: 'john.doe@example.com', - firstName: 'John', - lastName: 'Doe', - isEmailVerified: false, - }, + schema: { + example: { + id: 'user_abc123', + email: 'john.doe@example.com', + isEmailVerified: false, }, }, })