diff --git a/src/merchants/merchants.controller.ts b/src/merchants/merchants.controller.ts index ee8e40c..3d73ba0 100644 --- a/src/merchants/merchants.controller.ts +++ b/src/merchants/merchants.controller.ts @@ -9,6 +9,7 @@ import { Param, UseGuards, Request, + NotFoundException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { MerchantsService } from './merchants.service'; @@ -100,4 +101,14 @@ export class MerchantsController { return this.merchants.setCorsOrigins(m.id, dto.origins); }); } + + @Delete('me/cors/:origin') + @Roles('admin', 'merchant') + async deleteCorsOrigin(@Request() req: any, @Param('origin') origin: string) { + const m = await this.merchants.findByWallet(req.user.walletAddress); + if (!m) throw new NotFoundException('Merchant not found'); + const removed = await this.merchants.deleteCorsOrigin(m.id, decodeURIComponent(origin)); + if (!removed) throw new NotFoundException('Origin not found in configured list'); + return { origins: await this.merchants.getCorsOrigins(m.id) }; + } } diff --git a/src/merchants/merchants.dto.ts b/src/merchants/merchants.dto.ts index 36db806..17640d3 100644 --- a/src/merchants/merchants.dto.ts +++ b/src/merchants/merchants.dto.ts @@ -4,6 +4,7 @@ import { IsOptional, IsUrl, IsArray, + ArrayMaxSize, Matches, MaxLength, ValidateIf, @@ -56,6 +57,11 @@ export class GenerateApiKeyDto { export class SetCorsOriginsDto { @IsArray() - @IsUrl({}, { each: true }) + @ArrayMaxSize(10, { message: 'A maximum of 10 CORS origins are allowed per merchant' }) + @IsUrl( + { protocols: ['https'], require_protocol: true, allow_localhost: false }, + { each: true, message: 'Each origin must be a valid HTTPS URL without localhost' }, + ) + @Matches(/^[^*]+$/, { each: true, message: 'Wildcard origins are not permitted' }) origins: string[]; } diff --git a/src/merchants/merchants.service.ts b/src/merchants/merchants.service.ts index 79399f0..7276dff 100644 --- a/src/merchants/merchants.service.ts +++ b/src/merchants/merchants.service.ts @@ -148,4 +148,12 @@ export class MerchantsService { await this.corsCache.invalidateMerchantCache(merchantId); return (updated.corsOrigins ?? []) as string[]; } + + async deleteCorsOrigin(merchantId: string, origin: string): Promise { + const current = await this.getCorsOrigins(merchantId); + const filtered = current.filter((o: string) => o !== origin); + if (filtered.length === current.length) return false; + await this.setCorsOrigins(merchantId, filtered); + return true; + } }