fix: use plain password in configuration for MailJet webhook url

This commit is contained in:
Björn Fromme
2026-04-30 11:44:17 +02:00
parent c0c8ad515c
commit 68900dd4ea
8 changed files with 63 additions and 14 deletions
@@ -26,6 +26,7 @@ final class MailjetNewsletterWebhookCommand extends Command
private const string ACTION_REMOVE = 'remove';
private const string ACTION_DEACTIVATE = 'deactivate';
private const string EVENT_TYPE_UNSUBSCRIBE = 'unsub';
private const string WEBHOOK_BASIC_AUTH_USER = 'mailjet';
private const string WEBHOOK_PATH = '/webhooks/mailjet/newsletter';
private const int WEBHOOK_VERSION = 2;
@@ -33,6 +34,8 @@ final class MailjetNewsletterWebhookCommand extends Command
private readonly MailjetApiClient $mailjetApiClient,
#[Autowire('%env(APP_BASE_URL)%')]
private readonly string $defaultBaseUrl,
#[Autowire('%env(MAILJET_WEBHOOK_BASIC_PASSWORD)%')]
private readonly string $basicAuthPassword,
private readonly LoggerInterface $logger,
) {
parent::__construct();
@@ -43,7 +46,7 @@ final class MailjetNewsletterWebhookCommand extends Command
$this
->addArgument('action', InputArgument::REQUIRED, 'One of: register, remove, deactivate')
->addArgument('callback_id', InputArgument::OPTIONAL, 'Callback ID required for remove/deactivate')
->addOption('url', null, InputOption::VALUE_REQUIRED, 'Override the webhook URL to register');
->addOption('url', null, InputOption::VALUE_REQUIRED, 'Override the webhook URL to register before Basic Auth credentials are added');
}
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -72,9 +75,10 @@ final class MailjetNewsletterWebhookCommand extends Command
private function register(SymfonyStyle $io, mixed $overrideUrl): int
{
$webhookUrl = $this->resolveWebhookUrl($overrideUrl);
$payload = $this->mailjetApiClient->createEventCallbackUrl(self::EVENT_TYPE_UNSUBSCRIBE, $webhookUrl, self::WEBHOOK_VERSION);
$authenticatedWebhookUrl = $this->prependBasicAuth($webhookUrl);
$payload = $this->mailjetApiClient->createEventCallbackUrl(self::EVENT_TYPE_UNSUBSCRIBE, $authenticatedWebhookUrl, self::WEBHOOK_VERSION);
$io->success(sprintf('Created Mailjet newsletter webhook at %s.', $webhookUrl));
$io->success(sprintf('Created Mailjet newsletter webhook at %s.', $authenticatedWebhookUrl));
$io->writeln($this->formatPayload($payload));
return Command::SUCCESS;
@@ -115,6 +119,47 @@ final class MailjetNewsletterWebhookCommand extends Command
return rtrim(trim($this->defaultBaseUrl), '/').self::WEBHOOK_PATH;
}
private function prependBasicAuth(string $url): string
{
if ('' === trim($this->basicAuthPassword)) {
throw new \InvalidArgumentException('MAILJET_WEBHOOK_BASIC_PASSWORD is not configured.');
}
$parts = parse_url($url);
if (false === is_array($parts) || false === isset($parts['scheme'], $parts['host'])) {
throw new \InvalidArgumentException('The webhook URL is not valid.');
}
$host = $parts['host'];
if (false === str_starts_with($host, '[') && true === str_contains($host, ':')) {
$host = sprintf('[%s]', $host);
}
$authenticatedUrl = sprintf(
'%s://%s:%s@%s',
$parts['scheme'],
rawurlencode(self::WEBHOOK_BASIC_AUTH_USER),
rawurlencode($this->basicAuthPassword),
$host,
);
if (isset($parts['port'])) {
$authenticatedUrl .= sprintf(':%d', $parts['port']);
}
$authenticatedUrl .= $parts['path'] ?? '';
if (isset($parts['query'])) {
$authenticatedUrl .= '?'.$parts['query'];
}
if (isset($parts['fragment'])) {
$authenticatedUrl .= '#'.$parts['fragment'];
}
return $authenticatedUrl;
}
private function resolveCallbackId(mixed $callbackId): int
{
if (true === is_int($callbackId) && $callbackId > 0) {
@@ -22,7 +22,7 @@ final class MailjetNewsletterWebhookController extends AbstractController
}
#[Route('/webhooks/mailjet/newsletter', name: 'app_webhook_mailjet_newsletter', methods: ['POST'])]
public function __invoke(Request $request): JsonResponse
public function index(Request $request): JsonResponse
{
$payload = json_decode($request->getContent(), true);
if (JSON_ERROR_NONE !== json_last_error() || false === is_array($payload)) {
@@ -51,8 +51,7 @@ final class MailjetNewsletterWebhookController extends AbstractController
}
/**
* @param array<mixed> $payload
*
* @param array<string, mixed> $payload
* @return list<array<string, mixed>>
*/
private function normalizeEvents(array $payload): array