fix: restore watermark image in email templates
All checks were successful
CI / build (push) Successful in 2m18s
CI / docker (push) Successful in 1m10s

The previous commit incorrectly removed the watermark — only the
style extraction into <style> blocks was requested. Restores the
watermark <img>, {{watermarkUrl}} placeholder resolution in both
EmailConnectorService and PasswordResetNotificationService, and
the corresponding test assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-26 15:14:35 +02:00
parent f681784e7e
commit c55427c22b
8 changed files with 68 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
package net.siegeln.cameleer.saas.notification;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
import net.siegeln.cameleer.saas.vendor.EmailConnectorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,11 +28,14 @@ public class PasswordResetNotificationService {
private final EmailConnectorService emailConnectorService;
private final LogtoManagementClient logtoClient;
private final ProvisioningProperties provisioningProps;
public PasswordResetNotificationService(EmailConnectorService emailConnectorService,
LogtoManagementClient logtoClient) {
LogtoManagementClient logtoClient,
ProvisioningProperties provisioningProps) {
this.emailConnectorService = emailConnectorService;
this.logtoClient = logtoClient;
this.provisioningProps = provisioningProps;
}
/**
@@ -110,8 +114,12 @@ public class PasswordResetNotificationService {
String content = new ClassPathResource("email-templates/password-reset-notification.html")
.getContentAsString(StandardCharsets.UTF_8);
String watermarkUrl = provisioningProps.publicProtocol() + "://"
+ provisioningProps.publicHost() + "/platform/assets/email-watermark.png";
String timestamp = ZonedDateTime.now(ZoneOffset.UTC).format(TIMESTAMP_FMT);
return content.replace("{{timestamp}}", timestamp);
return content
.replace("{{watermarkUrl}}", watermarkUrl)
.replace("{{timestamp}}", timestamp);
}
}

View File

@@ -1,6 +1,7 @@
package net.siegeln.cameleer.saas.vendor;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
@@ -19,9 +20,11 @@ public class EmailConnectorService {
private static final String SMTP_FACTORY_ID = "simple-mail-transfer-protocol";
private final LogtoManagementClient logtoClient;
private final ProvisioningProperties provisioningProps;
public EmailConnectorService(LogtoManagementClient logtoClient) {
public EmailConnectorService(LogtoManagementClient logtoClient, ProvisioningProperties provisioningProps) {
this.logtoClient = logtoClient;
this.provisioningProps = provisioningProps;
}
public record SmtpConfig(String host, int port, String username, String password, String fromEmail) {}
@@ -157,11 +160,14 @@ public class EmailConnectorService {
return "SignInAndRegister".equals(signInExp.get("signInMode"));
}
/** Load an email template from classpath. */
/** Load an email template from classpath and resolve the watermark URL placeholder. */
private String loadTemplate(String filename) {
try {
return new ClassPathResource("email-templates/" + filename)
String content = new ClassPathResource("email-templates/" + filename)
.getContentAsString(StandardCharsets.UTF_8);
String watermarkUrl = provisioningProps.publicProtocol() + "://"
+ provisioningProps.publicHost() + "/platform/assets/email-watermark.png";
return content.replace("{{watermarkUrl}}", watermarkUrl);
} catch (IOException e) {
throw new IllegalStateException("Failed to load email template: " + filename, e);
}

View File

@@ -10,7 +10,9 @@
.wrapper { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; max-width:480px; margin:0 auto; background:#fff; border-radius:8px; overflow:hidden; border:1px solid #e8e0d4; }
.header { background:#C6820E; padding:20px 24px; text-align:center; }
.header span { font-size:22px; font-weight:700; color:#fff; letter-spacing:0.5px; }
.content { padding:32px 24px 24px; }
.content { padding:32px 24px 24px; position:relative; overflow:hidden; }
.watermark { position:absolute; top:-30px; right:-50px; width:320px; height:320px; opacity:0.07; pointer-events:none; border:0; outline:none; }
.inner { position:relative; }
.title { color:#1a1a1a; font-size:16px; font-weight:600; margin:0 0 8px; }
.subtitle { color:#444; font-size:14px; line-height:1.6; margin:0 0 24px; }
.code-box { text-align:center; margin:0 0 24px; }
@@ -30,6 +32,8 @@
<span>Cameleer.io</span>
</div>
<div class="content">
<img src="{{watermarkUrl}}" class="watermark" alt="" />
<div class="inner">
<p class="title">Lost in the dunes?</p>
<p class="subtitle">No worries — enter this code to reset your password and get back on the trail.</p>
<div class="code-box">
@@ -39,6 +43,7 @@
</div>
<p class="hint">This code expires in 10 minutes. If you didn't request a password reset, you can safely ignore this email.</p>
<p class="detail">This password reset was requested for your Cameleer account. Cameleer is an observability platform for Apache Camel integrations providing real-time route tracing, message inspection, and performance monitoring. For security, this code can only be used once. If you continue to have trouble accessing your account, please contact your administrator for assistance.</p>
</div>
</div>
<div class="footer">
<p class="contact">Questions? Contact your administrator</p>

View File

@@ -10,7 +10,9 @@
.wrapper { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; max-width:480px; margin:0 auto; background:#fff; border-radius:8px; overflow:hidden; border:1px solid #e8e0d4; }
.header { background:#C6820E; padding:20px 24px; text-align:center; }
.header span { font-size:22px; font-weight:700; color:#fff; letter-spacing:0.5px; }
.content { padding:32px 24px 24px; }
.content { padding:32px 24px 24px; position:relative; overflow:hidden; }
.watermark { position:absolute; top:-30px; right:-50px; width:320px; height:320px; opacity:0.07; pointer-events:none; border:0; outline:none; }
.inner { position:relative; }
.title { color:#1a1a1a; font-size:16px; font-weight:600; margin:0 0 8px; }
.subtitle { color:#444; font-size:14px; line-height:1.6; margin:0 0 24px; }
.code-box { text-align:center; margin:0 0 24px; }
@@ -30,6 +32,8 @@
<span>Cameleer.io</span>
</div>
<div class="content">
<img src="{{watermarkUrl}}" class="watermark" alt="" />
<div class="inner">
<p class="title">Quick checkpoint</p>
<p class="subtitle">Here's your verification code. Just making sure it's really you at the reins.</p>
<div class="code-box">
@@ -39,6 +43,7 @@
</div>
<p class="hint">This code expires in 10 minutes.</p>
<p class="detail">You are receiving this email because a verification was requested on your Cameleer account. Cameleer is an observability platform for Apache Camel integrations providing real-time route tracing, message inspection, and performance monitoring. If you did not request this verification, please ignore this email or contact your administrator for assistance.</p>
</div>
</div>
<div class="footer">
<p class="contact">Questions? Contact your administrator</p>

View File

@@ -10,7 +10,9 @@
.wrapper { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; max-width:480px; margin:0 auto; background:#fff; border-radius:8px; overflow:hidden; border:1px solid #e8e0d4; }
.header { background:#C6820E; padding:20px 24px; text-align:center; }
.header span { font-size:22px; font-weight:700; color:#fff; letter-spacing:0.5px; }
.content { padding:32px 24px 24px; }
.content { padding:32px 24px 24px; position:relative; overflow:hidden; }
.watermark { position:absolute; top:-30px; right:-50px; width:320px; height:320px; opacity:0.07; pointer-events:none; border:0; outline:none; }
.inner { position:relative; }
.title { color:#1a1a1a; font-size:16px; font-weight:600; margin:0 0 8px; }
.subtitle { color:#444; font-size:14px; line-height:1.6; margin:0 0 16px; }
.mfa-note { background:#FDF6EC; border:1px solid #e8e0d4; border-radius:6px; padding:12px 16px; margin:0 0 16px; }
@@ -29,6 +31,8 @@
<span>Cameleer.io</span>
</div>
<div class="content">
<img src="{{watermarkUrl}}" class="watermark" alt="" />
<div class="inner">
<p class="title">Your password was reset</p>
<p class="subtitle">Your Cameleer account password was successfully changed on {{timestamp}}.</p>
<div class="mfa-note">
@@ -36,6 +40,7 @@
</div>
<p class="hint">If this wasn't you, contact your administrator immediately.</p>
<p class="detail">This is an automated security notification from your Cameleer account. Cameleer is an observability platform for Apache Camel integrations providing real-time route tracing, message inspection, and performance monitoring. You are receiving this notification because a password change was completed. For your security, we recommend reviewing your account activity and ensuring your credentials are kept safe.</p>
</div>
</div>
<div class="footer">
<p class="contact">Questions? Contact your administrator</p>

View File

@@ -10,7 +10,9 @@
.wrapper { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; max-width:480px; margin:0 auto; background:#fff; border-radius:8px; overflow:hidden; border:1px solid #e8e0d4; }
.header { background:#C6820E; padding:20px 24px; text-align:center; }
.header span { font-size:22px; font-weight:700; color:#fff; letter-spacing:0.5px; }
.content { padding:32px 24px 24px; }
.content { padding:32px 24px 24px; position:relative; overflow:hidden; }
.watermark { position:absolute; top:-30px; right:-50px; width:320px; height:320px; opacity:0.07; pointer-events:none; border:0; outline:none; }
.inner { position:relative; }
.title { color:#1a1a1a; font-size:16px; font-weight:600; margin:0 0 8px; }
.subtitle { color:#444; font-size:14px; line-height:1.6; margin:0 0 24px; }
.code-box { text-align:center; margin:0 0 24px; }
@@ -30,6 +32,8 @@
<span>Cameleer.io</span>
</div>
<div class="content">
<img src="{{watermarkUrl}}" class="watermark" alt="" />
<div class="inner">
<p class="title">Welcome to the caravan!</p>
<p class="subtitle">Enter this code to verify your email and claim your spot. The dunes wait for no one.</p>
<div class="code-box">
@@ -39,6 +43,7 @@
</div>
<p class="hint">This code expires in 10 minutes. If you didn't request this, you can safely ignore this email — no camels were harmed.</p>
<p class="detail">Cameleer is an observability platform for Apache Camel integrations. It provides real-time route tracing, message inspection, and performance monitoring to help your team debug and optimize integration flows. Your account gives you access to your dedicated Cameleer instance where you can connect your Camel applications and start monitoring immediately.</p>
</div>
</div>
<div class="footer">
<p class="contact">Questions? Contact your administrator</p>

View File

@@ -10,7 +10,9 @@
.wrapper { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; max-width:480px; margin:0 auto; background:#fff; border-radius:8px; overflow:hidden; border:1px solid #e8e0d4; }
.header { background:#C6820E; padding:20px 24px; text-align:center; }
.header span { font-size:22px; font-weight:700; color:#fff; letter-spacing:0.5px; }
.content { padding:32px 24px 24px; }
.content { padding:32px 24px 24px; position:relative; overflow:hidden; }
.watermark { position:absolute; top:-30px; right:-50px; width:320px; height:320px; opacity:0.07; pointer-events:none; border:0; outline:none; }
.inner { position:relative; }
.title { color:#1a1a1a; font-size:16px; font-weight:600; margin:0 0 8px; }
.subtitle { color:#444; font-size:14px; line-height:1.6; margin:0 0 24px; }
.code-box { text-align:center; margin:0 0 24px; }
@@ -30,6 +32,8 @@
<span>Cameleer.io</span>
</div>
<div class="content">
<img src="{{watermarkUrl}}" class="watermark" alt="" />
<div class="inner">
<p class="title">Back at the oasis already?</p>
<p class="subtitle">Here's your sign-in code. The caravan master is checking credentials.</p>
<div class="code-box">
@@ -39,6 +43,7 @@
</div>
<p class="hint">This code expires in 10 minutes.</p>
<p class="detail">You are receiving this email because a sign-in attempt was made on your Cameleer account. Cameleer is an observability platform for Apache Camel integrations providing real-time route tracing, message inspection, and performance monitoring. If you did not initiate this sign-in, please ignore this email or contact your administrator.</p>
</div>
</div>
<div class="footer">
<p class="contact">Questions? Contact your administrator</p>

View File

@@ -45,6 +45,25 @@ class EmailTemplateLoadingTest {
}
}
@Test
void templatesContainWatermarkPlaceholder() throws IOException {
for (String path : TEMPLATE_FILES) {
String content = new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8);
assertTrue(content.contains("{{watermarkUrl}}"),
path + " must contain {{watermarkUrl}} placeholder");
}
}
@Test
void watermarkPlaceholderIsReplaced() throws IOException {
String content = new ClassPathResource("email-templates/register.html")
.getContentAsString(StandardCharsets.UTF_8);
String resolved = content.replace("{{watermarkUrl}}",
"https://example.com/platform/assets/email-watermark.png");
assertFalse(resolved.contains("{{watermarkUrl}}"));
assertTrue(resolved.contains("https://example.com/platform/assets/email-watermark.png"));
}
@Test
void templatesContainBrandElements() throws IOException {
for (String path : TEMPLATE_FILES) {