Files
backend/src/public-sms/sms-partner.guard.ts
T
Alireza HassaniandCursor f4295e780c Add partner SMS gateway via Gama SendQuick and document it.
Expose POST /public/sms/send with API key + domain allowlist for external backends like Balout, wire Meshkee OTP/message sends to Gama, and publish Partner SMS docs on /docs/website.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:09:21 +03:30

49 lines
1.5 KiB
TypeScript

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<string>('SMS_PARTNERS'));
if (partners.size === 0) {
throw new UnauthorizedException('SMS partner gateway is not configured');
}
const req = context.switchToHttp().getRequest<Request>();
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;
}
}