From 54ab238cb7590282cb9982b010101bdcb22280ac Mon Sep 17 00:00:00 2001 From: opeaceooo Date: Sun, 18 Oct 2026 13:56:17 +0100 Subject: [PATCH] Fix unreachable code and missing validation across multiple services - Fix #749: Reorder JWT_SECRET validation to check placeholder values before length - Fix #761: Add missing imports for MetricsService and SentryService, define LOCK_RENEWAL_INTERVAL_MS constant - Fix #757: Map IPFS pin status to deliverable status so PENDING and FAILED states are reachable - Fix #764: Add validation to reject even juror counts to prevent vote ties These changes ensure all code paths are reachable and proper validation prevents edge cases. --- backend/src/config/env.config.ts | 4 ++-- backend/src/deliverable/deliverable.service.ts | 17 ++++++++++++++++- backend/src/dispute/dispute-saga.service.ts | 3 +++ backend/src/gig/gig-expiry-worker.service.ts | 3 +++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/src/config/env.config.ts b/backend/src/config/env.config.ts index 6f24c3f..e84850c 100644 --- a/backend/src/config/env.config.ts +++ b/backend/src/config/env.config.ts @@ -438,10 +438,10 @@ const EnvSchema = z if (isProduction) { if (!secret || secret.trim() === '') { issue('JWT_SECRET', 'JWT_SECRET is required in production'); - } else if (secret.length < 32) { - issue('JWT_SECRET', 'JWT_SECRET must be at least 32 characters in production'); } else if (PLACEHOLDER_JWT_SECRETS.has(secret.trim().toLowerCase())) { issue('JWT_SECRET', 'JWT_SECRET must not be a placeholder value in production'); + } else if (secret.length < 32) { + issue('JWT_SECRET', 'JWT_SECRET must be at least 32 characters in production'); } } else { // Development/test: if a value is explicitly provided it must still meet minimum length diff --git a/backend/src/deliverable/deliverable.service.ts b/backend/src/deliverable/deliverable.service.ts index 4274bac..8a9d2a2 100644 --- a/backend/src/deliverable/deliverable.service.ts +++ b/backend/src/deliverable/deliverable.service.ts @@ -14,6 +14,7 @@ import { GigService } from '../gig/gig.service'; import { GigStatus } from '../gig/gig.entity'; import { UploadDeliverableDto } from './deliverable.dto'; import { Deliverable, DeliverableStatus } from './deliverable.entity'; +import { PinStatus } from '../ipfs-pinning/ipfs-pinning.types'; const DELIVERABLE_KEY_PREFIX = 'deliverable:'; const DELIVERABLES_BY_GIG_PREFIX = 'deliverables:gig:'; @@ -49,6 +50,7 @@ export class DeliverableService { filename: dto.filename, }); + const deliverableStatus = this.mapPinStatusToDeliverableStatus(pinResult.status); const deliverable: Deliverable = { id, gigId: dto.gigId, @@ -56,7 +58,7 @@ export class DeliverableService { cid: pinResult.cid, filename: dto.filename, size: pinResult.size, - status: DeliverableStatus.PINNED, + status: deliverableStatus, createdAt: now, updatedAt: now, }; @@ -181,4 +183,17 @@ export class DeliverableService { private gigKey(gigId: string): string { return `${DELIVERABLES_BY_GIG_PREFIX}${gigId}`; } + + private mapPinStatusToDeliverableStatus(pinStatus: PinStatus): DeliverableStatus { + switch (pinStatus) { + case PinStatus.HEALTHY: + return DeliverableStatus.PINNED; + case PinStatus.DEGRADED: + return DeliverableStatus.PENDING; + case PinStatus.FAILED: + return DeliverableStatus.FAILED; + default: + return DeliverableStatus.FAILED; + } + } } diff --git a/backend/src/dispute/dispute-saga.service.ts b/backend/src/dispute/dispute-saga.service.ts index 8e0626c..e3f93ea 100644 --- a/backend/src/dispute/dispute-saga.service.ts +++ b/backend/src/dispute/dispute-saga.service.ts @@ -521,6 +521,9 @@ export class DisputeSagaService implements OnModuleInit { if (unique.length < 3) { throw new BadRequestException('At least 3 distinct juror addresses are required'); } + if (unique.length % 2 === 0) { + throw new BadRequestException('Juror count must be odd to prevent vote ties'); + } // Prevent escrow parties from being assigned as jurors to avoid conflicts of interest const escrow = await this.escrowService.findById(saga.escrowId); diff --git a/backend/src/gig/gig-expiry-worker.service.ts b/backend/src/gig/gig-expiry-worker.service.ts index f487c22..4231fb8 100644 --- a/backend/src/gig/gig-expiry-worker.service.ts +++ b/backend/src/gig/gig-expiry-worker.service.ts @@ -5,8 +5,11 @@ import { DEFAULT_GIG_EXPIRY_SWEEP_INTERVAL_MS } from './gig.entity'; import { DistributedLockService } from '../common/redis/distributed-lock.service'; import { mapWithConcurrency, countRejected } from '../common/concurrency'; import { config } from '../config/env.config'; +import { MetricsService } from '../monitoring/metrics.service'; +import { SentryService } from '../sentry/sentry.service'; const LOCK_KEY = 'lock:gig-expiry-sweep'; +const LOCK_RENEWAL_INTERVAL_MS = 10_000; /** * Periodically sweeps the DB for open gig solicitations whose response deadline has