diff --git a/.changeset/flat-ads-know.md b/.changeset/flat-ads-know.md new file mode 100644 index 00000000..bd254679 --- /dev/null +++ b/.changeset/flat-ads-know.md @@ -0,0 +1,34 @@ +--- +"@comet/brevo-api": major +--- + +The `allowedRedirectUrl` must now be configured within the resolveConfig for each specific scope, instead of being set once for all scopes in the brevo config. + +```diff + BrevoModule.register({ + brevo: { + resolveConfig: (scope: EmailCampaignContentScope) => { + // change config based on scope - for example different sender email + // this is just to show you can use the scope to change the config but it has no real use in this example + if (scope.domain === "main") { + return { + apiKey: config.brevo.apiKey, + doubleOptInTemplateId: config.brevo.doubleOptInTemplateId, + sender: { name: config.brevo.sender.name, email: config.brevo.sender.email }, ++ allowedRedirectUrl: config.brevo.allowedRedirectUrl, + }; + } else { + return { + apiKey: config.brevo.apiKey, + doubleOptInTemplateId: config.brevo.doubleOptInTemplateId, + sender: { name: config.brevo.sender.name, email: config.brevo.sender.email }, ++ allowedRedirectUrl: config.brevo.allowedRedirectUrl, + }; + } + }, + BrevoContactAttributes, + BrevoContactFilterAttributes, +- allowedRedirectUrl: config.brevo.allowedRedirectUrl, + }, + }) +``` diff --git a/demo/api/src/app.module.ts b/demo/api/src/app.module.ts index 340f0ca2..76c97286 100644 --- a/demo/api/src/app.module.ts +++ b/demo/api/src/app.module.ts @@ -145,18 +145,19 @@ export class AppModule { apiKey: config.brevo.apiKey, doubleOptInTemplateId: config.brevo.doubleOptInTemplateId, sender: { name: config.brevo.sender.name, email: config.brevo.sender.email }, + allowedRedirectUrl: config.brevo.allowedRedirectUrl, }; } else { return { apiKey: config.brevo.apiKey, doubleOptInTemplateId: config.brevo.doubleOptInTemplateId, sender: { name: config.brevo.sender.name, email: config.brevo.sender.email }, + allowedRedirectUrl: config.brevo.allowedRedirectUrl, }; } }, BrevoContactAttributes, BrevoContactFilterAttributes, - allowedRedirectUrl: config.brevo.allowedRedirectUrl, }, ecgRtrList: { apiKey: config.ecgRtrList.apiKey, diff --git a/demo/api/src/brevo-contact/dto/brevo-contact-subscribe.input.ts b/demo/api/src/brevo-contact/dto/brevo-contact-subscribe.input.ts index 936a4ad0..52410b55 100644 --- a/demo/api/src/brevo-contact/dto/brevo-contact-subscribe.input.ts +++ b/demo/api/src/brevo-contact/dto/brevo-contact-subscribe.input.ts @@ -10,7 +10,7 @@ export class BrevoContactSubscribeInput { email: string; @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) - @IsValidRedirectURL() + @IsValidRedirectURL(EmailContactSubscribeScope) redirectionUrl: string; @ValidateNested() diff --git a/packages/api/src/brevo-contact/brevo-contact.module.ts b/packages/api/src/brevo-contact/brevo-contact.module.ts index 916aa655..6a4b8581 100644 --- a/packages/api/src/brevo-contact/brevo-contact.module.ts +++ b/packages/api/src/brevo-contact/brevo-contact.module.ts @@ -25,7 +25,7 @@ export class BrevoContactModule { static register({ BrevoContactAttributes, Scope, TargetGroup }: BrevoContactModuleConfig): DynamicModule { const BrevoContact = BrevoContactFactory.create({ BrevoContactAttributes }); const BrevoContactSubscribeInput = SubscribeInputFactory.create({ BrevoContactAttributes, Scope }); - const [BrevoContactInput, BrevoContactUpdateInput] = BrevoContactInputFactory.create({ BrevoContactAttributes }); + const [BrevoContactInput, BrevoContactUpdateInput] = BrevoContactInputFactory.create({ BrevoContactAttributes, Scope }); const BrevoContactResolver = createBrevoContactResolver({ BrevoContact, BrevoContactSubscribeInput, diff --git a/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts b/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts index 2dc4618d..109069b5 100644 --- a/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts +++ b/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts @@ -4,7 +4,7 @@ import { Field, InputType } from "@nestjs/graphql"; import { Type as TypeTransformer } from "class-transformer"; import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUrl, ValidateNested } from "class-validator"; -import { BrevoContactAttributesInterface } from "../../types"; +import { BrevoContactAttributesInterface, EmailCampaignScopeInterface } from "../../types"; import { IsValidRedirectURL } from "../validator/redirect-url.validator"; export interface BrevoContactInputInterface { @@ -22,8 +22,10 @@ export interface BrevoContactUpdateInputInterface { export class BrevoContactInputFactory { static create({ BrevoContactAttributes, + Scope, }: { BrevoContactAttributes?: Type; + Scope: Type; }): [Type, Type>] { @InputType({ isAbstract: true, @@ -41,7 +43,7 @@ export class BrevoContactInputFactory { @Field() @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) - @IsValidRedirectURL() + @IsValidRedirectURL(Scope) redirectionUrl: string; } diff --git a/packages/api/src/brevo-contact/dto/subscribe-input.factory.ts b/packages/api/src/brevo-contact/dto/subscribe-input.factory.ts index 186586a8..fe25b027 100644 --- a/packages/api/src/brevo-contact/dto/subscribe-input.factory.ts +++ b/packages/api/src/brevo-contact/dto/subscribe-input.factory.ts @@ -27,7 +27,7 @@ export class SubscribeInputFactory { @Field() @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) - @IsValidRedirectURL() + @IsValidRedirectURL(Scope) redirectionUrl: string; } diff --git a/packages/api/src/brevo-contact/validator/redirect-url.validator.ts b/packages/api/src/brevo-contact/validator/redirect-url.validator.ts index 943dc362..9acf9363 100644 --- a/packages/api/src/brevo-contact/validator/redirect-url.validator.ts +++ b/packages/api/src/brevo-contact/validator/redirect-url.validator.ts @@ -1,10 +1,11 @@ import { Inject, Injectable } from "@nestjs/common"; -import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; +import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; +import { EmailCampaignScopeInterface } from "src/types"; import { BrevoModuleConfig } from "../../config/brevo-module.config"; import { BREVO_MODULE_CONFIG } from "../../config/brevo-module.constants"; -export const IsValidRedirectURL = (validationOptions?: ValidationOptions) => { +export const IsValidRedirectURL = (scope: EmailCampaignScopeInterface, validationOptions?: ValidationOptions) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (object: Record, propertyName: string): void => { registerDecorator({ @@ -12,7 +13,7 @@ export const IsValidRedirectURL = (validationOptions?: ValidationOptions) => { propertyName, options: validationOptions, validator: IsValidRedirectURLConstraint, - constraints: [], + constraints: [scope], }); }; }; @@ -22,10 +23,18 @@ export const IsValidRedirectURL = (validationOptions?: ValidationOptions) => { export class IsValidRedirectURLConstraint implements ValidatorConstraintInterface { constructor(@Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig) {} - async validate(urlToValidate: string): Promise { - if (urlToValidate?.startsWith(this.config.brevo.allowedRedirectUrl)) { + async validate(urlToValidate: string, args: ValidationArguments): Promise { + const [scope] = args.constraints; + const configForScope = this.config.brevo.resolveConfig(scope); + + if (!configForScope) { + throw Error("Scope does not exist"); + } + + if (urlToValidate?.startsWith(configForScope.allowedRedirectUrl)) { return true; } + return false; } diff --git a/packages/api/src/config/brevo-module.config.ts b/packages/api/src/config/brevo-module.config.ts index fa5a29b4..499d2f46 100644 --- a/packages/api/src/config/brevo-module.config.ts +++ b/packages/api/src/config/brevo-module.config.ts @@ -13,10 +13,10 @@ export interface BrevoModuleConfig { name: string; email: string; }; + allowedRedirectUrl: string; }; BrevoContactAttributes?: Type; BrevoContactFilterAttributes?: Type; - allowedRedirectUrl: string; }; ecgRtrList: { apiKey: string;