From aaf237d111717792b5ffb13231912ce07f00800c Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 12 Aug 2024 09:41:14 +0200 Subject: [PATCH] use quarkus mailer for password recovery --- commafeed-server/TODO.md | 3 +- commafeed-server/pom.xml | 15 ++--- .../com/commafeed/CommaFeedConfiguration.java | 27 +++------ .../backend/service/MailService.java | 56 ++----------------- .../frontend/resource/ServerREST.java | 2 +- .../commafeed/frontend/resource/UserREST.java | 4 ++ .../frontend/servlet/LogoutServlet.java | 11 ++-- .../src/main/resources/application.properties | 7 +-- .../commafeed/integration/rest/UserIT.java | 52 ++++++++--------- 9 files changed, 53 insertions(+), 124 deletions(-) diff --git a/commafeed-server/TODO.md b/commafeed-server/TODO.md index 192376e1..6c77457f 100644 --- a/commafeed-server/TODO.md +++ b/commafeed-server/TODO.md @@ -3,10 +3,9 @@ TODO MVP: -- quarkus mailer for smtp - - https://quarkus.io/guides/mailer - cookie duration too short - https://github.com/quarkusio/quarkus/issues/42463 + - Rewrite cookie with https://quarkus.io/guides/rest#request-or-response-filters in the mean time - mvn profile instead of -Dquarkus.datasource.db-kind - update github actions diff --git a/commafeed-server/pom.xml b/commafeed-server/pom.xml index 4ab11efd..e01adf79 100644 --- a/commafeed-server/pom.xml +++ b/commafeed-server/pom.xml @@ -255,6 +255,10 @@ io.quarkus quarkus-websockets + + io.quarkus + quarkus-mailer + io.quarkus quarkus-hibernate-orm @@ -331,16 +335,11 @@ passay 1.6.4 - redis.clients jedis 5.1.4 - - com.sun.mail - jakarta.mail - com.rometools @@ -456,12 +455,6 @@ rest-assured test - - com.icegreen - greenmail-junit5 - 2.0.1 - test - org.awaitility awaitility diff --git a/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java b/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java index eb64073f..d1b92360 100644 --- a/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -49,6 +49,14 @@ public interface CommaFeedConfiguration { @WithDefault("false") boolean imageProxyEnabled(); + /** + * Enable password recovery via email. + * + * Quarkus mailer will need to be configured. + */ + @WithDefault("false") + boolean passwordRecoveryEnabled(); + /** * Message displayed in a notification at the bottom of the page. */ @@ -84,11 +92,6 @@ public interface CommaFeedConfiguration { */ Websocket websocket(); - /** - * SMTP settings for password recovery. - */ - Optional smtp(); - /** * Redis settings to enable caching. This is only really useful on instances with a lot of users. */ @@ -207,20 +210,6 @@ public interface CommaFeedConfiguration { boolean createDemoAccount(); } - interface Smtp { - String host(); - - int port(); - - boolean tls(); - - String userName(); - - String password(); - - String fromAddress(); - } - interface Websocket { /** * Enable websocket connection so the server can notify the web client that there are new entries for your feeds. diff --git a/commafeed-server/src/main/java/com/commafeed/backend/service/MailService.java b/commafeed-server/src/main/java/com/commafeed/backend/service/MailService.java index 758945aa..cef02d1f 100644 --- a/commafeed-server/src/main/java/com/commafeed/backend/service/MailService.java +++ b/commafeed-server/src/main/java/com/commafeed/backend/service/MailService.java @@ -1,64 +1,20 @@ package com.commafeed.backend.service; -import java.util.Optional; -import java.util.Properties; - -import com.commafeed.CommaFeedConfiguration; -import com.commafeed.CommaFeedConfiguration.Smtp; import com.commafeed.backend.model.User; +import io.quarkus.mailer.Mail; +import io.quarkus.mailer.Mailer; import jakarta.inject.Singleton; -import jakarta.mail.Authenticator; -import jakarta.mail.Message; -import jakarta.mail.PasswordAuthentication; -import jakarta.mail.Session; -import jakarta.mail.Transport; -import jakarta.mail.internet.InternetAddress; -import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; -/** - * Mailing service - * - */ @RequiredArgsConstructor @Singleton public class MailService { - private final CommaFeedConfiguration config; - - public void sendMail(User user, String subject, String content) throws Exception { - Optional settings = config.smtp(); - if (settings.isEmpty()) { - throw new IllegalArgumentException("SMTP settings not configured"); - } - - final String username = settings.get().userName(); - final String password = settings.get().password(); - final String fromAddress = Optional.ofNullable(settings.get().fromAddress()).orElse(settings.get().userName()); - - String dest = user.getEmail(); - - Properties props = new Properties(); - props.put("mail.smtp.auth", "true"); - props.put("mail.smtp.starttls.enable", String.valueOf(settings.get().tls())); - props.put("mail.smtp.host", settings.get().host()); - props.put("mail.smtp.port", String.valueOf(settings.get().port())); - - Session session = Session.getInstance(props, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - - Message message = new MimeMessage(session); - message.setFrom(new InternetAddress(fromAddress, "CommaFeed")); - message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(dest)); - message.setSubject("CommaFeed - " + subject); - message.setContent(content, "text/html; charset=utf-8"); - - Transport.send(message); + private final Mailer mailer; + public void sendMail(User user, String subject, String content) { + Mail mail = Mail.withHtml(user.getEmail(), "CommaFeed - " + subject, content); + mailer.send(mail); } } diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java index 881a7a04..4a6d64d7 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java @@ -56,7 +56,7 @@ public class ServerREST { infos.setGitCommit(version.getGitCommit()); infos.setAllowRegistrations(config.users().allowRegistrations()); infos.setGoogleAnalyticsCode(config.googleAnalyticsTrackingCode().orElse(null)); - infos.setSmtpEnabled(config.smtp().isPresent()); + infos.setSmtpEnabled(config.passwordRecoveryEnabled()); infos.setDemoAccountEnabled(config.users().createDemoAccount()); infos.setWebsocketEnabled(config.websocket().enabled()); infos.setWebsocketPingInterval(config.websocket().pingInterval().toMillis()); diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java index b391631f..0162c089 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -267,6 +267,10 @@ public class UserREST { @Transactional @Operation(summary = "send a password reset email") public Response sendPasswordReset(@Valid @Parameter(required = true) PasswordResetRequest req) { + if (!config.passwordRecoveryEnabled()) { + throw new IllegalArgumentException("Password recovery is not enabled on this CommaFeed instance"); + } + User user = userDAO.findByEmail(req.getEmail()); if (user == null) { return Response.ok().build(); diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/servlet/LogoutServlet.java b/commafeed-server/src/main/java/com/commafeed/frontend/servlet/LogoutServlet.java index 09a7b426..592074b4 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/servlet/LogoutServlet.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/servlet/LogoutServlet.java @@ -14,18 +14,19 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.NewCookie; import jakarta.ws.rs.core.Response; -import lombok.RequiredArgsConstructor; @Path("/logout") @PermitAll -@RequiredArgsConstructor @Singleton public class LogoutServlet { - @ConfigProperty(name = "quarkus.http.auth.form.cookie-name") - String cookieName; - private final CommaFeedConfiguration config; + private final String cookieName; + + public LogoutServlet(CommaFeedConfiguration config, @ConfigProperty(name = "quarkus.http.auth.form.cookie-name") String cookieName) { + this.config = config; + this.cookieName = cookieName; + } @GET public Response get() { diff --git a/commafeed-server/src/main/resources/application.properties b/commafeed-server/src/main/resources/application.properties index 82a33f85..c8301671 100644 --- a/commafeed-server/src/main/resources/application.properties +++ b/commafeed-server/src/main/resources/application.properties @@ -35,12 +35,7 @@ quarkus.shutdown.timeout=5s %test.quarkus.log.category."liquibase".level=WARN %test.commafeed.users.create-demo-account=true %test.commafeed.users.allow-registrations=true -%test.commafeed.smtp.host=localhost -%test.commafeed.smtp.port=3025 -%test.commafeed.smtp.tls=false -%test.commafeed.smtp.user-name=user -%test.commafeed.smtp.password=pass -%test.commafeed.smtp.from-address=noreply@commafeed.com +%test.commafeed.password-recovery-enabled=true # prod profile overrides diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java index ae077b1c..60f6d317 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java @@ -1,52 +1,44 @@ package com.commafeed.integration.rest; -import org.junit.jupiter.api.AfterEach; +import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import com.commafeed.frontend.model.request.PasswordResetRequest; import com.commafeed.integration.BaseIT; -import com.icegreen.greenmail.util.GreenMail; -import com.icegreen.greenmail.util.ServerSetupTest; +import io.quarkus.mailer.MockMailbox; import io.quarkus.test.junit.QuarkusTest; -import jakarta.mail.internet.MimeMessage; +import io.vertx.ext.mail.MailMessage; +import jakarta.inject.Inject; import jakarta.ws.rs.client.Entity; @QuarkusTest class UserIT extends BaseIT { - @Nested - class PasswordReset { + @Inject + MockMailbox mailbox; - private GreenMail greenMail; + @BeforeEach + void setup() { + mailbox.clear(); + } - @BeforeEach - void setup() { - this.greenMail = new GreenMail(ServerSetupTest.SMTP); - this.greenMail.start(); - this.greenMail.setUser("noreply@commafeed.com", "user", "pass"); - } + @Test + void resetPassword() { + PasswordResetRequest req = new PasswordResetRequest(); + req.setEmail("admin@commafeed.com"); - @AfterEach - void cleanup() { - this.greenMail.stop(); - } + getClient().target(getApiBaseUrl() + "user/passwordReset").request().post(Entity.json(req), Void.TYPE); - @Test - void resetPassword() throws Exception { - PasswordResetRequest req = new PasswordResetRequest(); - req.setEmail("admin@commafeed.com"); + List mails = mailbox.getMailMessagesSentTo("admin@commafeed.com"); + Assertions.assertEquals(1, mails.size()); - getClient().target(getApiBaseUrl() + "user/passwordReset").request().post(Entity.json(req), Void.TYPE); - - MimeMessage message = greenMail.getReceivedMessages()[0]; - Assertions.assertEquals("CommaFeed - Password recovery", message.getSubject()); - Assertions.assertTrue(message.getContent().toString().startsWith("You asked for password recovery for account 'admin'")); - Assertions.assertEquals("CommaFeed ", message.getFrom()[0].toString()); - Assertions.assertEquals("admin@commafeed.com", message.getAllRecipients()[0].toString()); - } + MailMessage message = mails.get(0); + Assertions.assertEquals("CommaFeed - Password recovery", message.getSubject()); + Assertions.assertTrue(message.getHtml().startsWith("You asked for password recovery for account 'admin'")); + Assertions.assertEquals("admin@commafeed.com", message.getTo().get(0)); } }