feat: test SMTP connection on save and retain password on edit
Adds testSmtpConnection() that performs EHLO + auth via JavaMailSender before persisting to Logto — saves fail fast with a clear error if SMTP credentials are wrong. Password is now optional when editing: if left blank the backend fetches the existing password from Logto's connector config, so users can update host/port/fromEmail without re-entering the password every time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ public class EmailConnectorController {
|
||||
@NotBlank String host,
|
||||
@Min(1) @Max(65535) int port,
|
||||
@NotBlank String username,
|
||||
@NotBlank String password,
|
||||
String password,
|
||||
@NotBlank @Email String fromEmail,
|
||||
Boolean registrationEnabled
|
||||
) {}
|
||||
@@ -76,11 +76,28 @@ public class EmailConnectorController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<EmailConnectorResponse> save(@Valid @RequestBody SmtpConfigRequest request) {
|
||||
public ResponseEntity<?> save(@Valid @RequestBody SmtpConfigRequest request) {
|
||||
// Resolve password: use provided value, or fall back to existing password from Logto
|
||||
String password = request.password();
|
||||
if (password == null || password.isBlank()) {
|
||||
password = emailConnectorService.getExistingPassword();
|
||||
if (password == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Password is required for new configuration"));
|
||||
}
|
||||
}
|
||||
|
||||
var smtp = new EmailConnectorService.SmtpConfig(
|
||||
request.host(), request.port(), request.username(),
|
||||
request.password(), request.fromEmail()
|
||||
password, request.fromEmail()
|
||||
);
|
||||
|
||||
// Test SMTP connection before saving
|
||||
try {
|
||||
emailConnectorService.testSmtpConnection(smtp);
|
||||
} catch (IllegalStateException e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||||
}
|
||||
|
||||
var status = emailConnectorService.saveSmtpConnector(smtp, request.registrationEnabled());
|
||||
return ResponseEntity.ok(EmailConnectorResponse.from(status));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -12,6 +13,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@Service
|
||||
public class EmailConnectorService {
|
||||
@@ -68,6 +70,55 @@ public class EmailConnectorService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the existing SMTP password from Logto, or null if not configured.
|
||||
* Used to retain the password when the user edits other fields without re-entering it.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public String getExistingPassword() {
|
||||
var connectors = logtoClient.listConnectors();
|
||||
var emailConnector = connectors.stream()
|
||||
.filter(c -> "Email".equals(c.get("type")))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (emailConnector == null) return null;
|
||||
var config = (Map<String, Object>) emailConnector.getOrDefault("config", Map.of());
|
||||
var auth = (Map<String, Object>) config.getOrDefault("auth", Map.of());
|
||||
String pass = String.valueOf(auth.getOrDefault("pass", ""));
|
||||
return pass.isEmpty() ? null : pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SMTP connection by performing EHLO + auth. Throws on failure.
|
||||
*/
|
||||
public void testSmtpConnection(SmtpConfig smtp) {
|
||||
var sender = new JavaMailSenderImpl();
|
||||
sender.setHost(smtp.host());
|
||||
sender.setPort(smtp.port());
|
||||
sender.setUsername(smtp.username());
|
||||
sender.setPassword(smtp.password());
|
||||
sender.setDefaultEncoding("UTF-8");
|
||||
|
||||
Properties props = sender.getJavaMailProperties();
|
||||
props.put("mail.transport.protocol", "smtp");
|
||||
props.put("mail.smtp.auth", "true");
|
||||
if (smtp.port() == 465) {
|
||||
props.put("mail.smtp.ssl.enable", "true");
|
||||
} else {
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
}
|
||||
props.put("mail.smtp.timeout", "10000");
|
||||
props.put("mail.smtp.connectiontimeout", "10000");
|
||||
|
||||
try {
|
||||
sender.testConnection();
|
||||
log.info("SMTP connection test successful: {}:{}", smtp.host(), smtp.port());
|
||||
} catch (Exception e) {
|
||||
log.warn("SMTP connection test failed: {}:{} — {}", smtp.host(), smtp.port(), e.getMessage());
|
||||
throw new IllegalStateException("SMTP connection failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Create or update the SMTP email connector. Returns the connector status. */
|
||||
public EmailConnectorStatus saveSmtpConnector(SmtpConfig smtp, Boolean registrationEnabled) {
|
||||
var connectorConfig = buildSmtpConfig(smtp);
|
||||
|
||||
Reference in New Issue
Block a user