import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; import { normalizePartnerDomain, parseSmsPartners, timingSafeEqualString, } from './sms-partners.util'; export const SMS_PARTNER_DOMAIN_KEY = 'smsPartnerDomain'; @Injectable() export class SmsPartnerGuard implements CanActivate { constructor(private readonly config: ConfigService) {} canActivate(context: ExecutionContext): boolean { const partners = parseSmsPartners(this.config.get('SMS_PARTNERS')); if (partners.size === 0) { throw new UnauthorizedException('SMS partner gateway is not configured'); } const req = context.switchToHttp().getRequest(); const providedKey = String(req.headers['x-api-key'] ?? '').trim(); if (!providedKey) { throw new UnauthorizedException('Missing X-Api-Key'); } const body = (req.body ?? {}) as { domain?: unknown }; const domain = normalizePartnerDomain(String(body.domain ?? '')); if (!domain) { throw new UnauthorizedException('Missing or invalid domain'); } const expectedKey = partners.get(domain); if (!expectedKey || !timingSafeEqualString(providedKey, expectedKey)) { throw new UnauthorizedException('Invalid API key or domain'); } (req as Request & { [SMS_PARTNER_DOMAIN_KEY]?: string })[SMS_PARTNER_DOMAIN_KEY] = domain; return true; } }