From b10719e4c69b489830001c9707f6e2eba265abad Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 21 Aug 2019 11:09:54 +0300 Subject: Settings API --- src/main/java/com/juick/server/EmailManager.java | 9 ++- src/main/java/com/juick/server/api/Service.java | 2 +- src/main/java/com/juick/server/api/Users.java | 77 +++++++++++++++++++++- .../java/com/juick/server/tests/ServerTests.java | 37 ++++++++++- 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/juick/server/EmailManager.java b/src/main/java/com/juick/server/EmailManager.java index 3d17a041..bd77d8d6 100644 --- a/src/main/java/com/juick/server/EmailManager.java +++ b/src/main/java/com/juick/server/EmailManager.java @@ -119,9 +119,9 @@ public class EmailManager implements NotificationListener { String plainText = renderPlaintext(formatPost(msg), formatUrl(msg)).orElseThrow(IllegalStateException::new); String hash = userService.getHashByUID(userService.getUserByEmail(email).getUid()); String htmlText = renderHtml(MessageUtils.formatHtml(msg), formatUrl(msg), msg, hash).orElseThrow(IllegalStateException::new); - sendEmail(email, subject, plainText, htmlText, headers); + sendEmail(StringUtils.EMPTY, email, subject, plainText, htmlText, headers); } - public void sendEmail(String to, String subject, String textPart, String htmlPart, Map messageHeaders) { + public boolean sendEmail(String from, String to, String subject, String textPart, String htmlPart, Map messageHeaders) { Properties prop = System.getProperties(); prop.put("mail.smtp.starttls.enable", "true"); Session session = Session.getDefaultInstance(prop); @@ -134,7 +134,8 @@ public class EmailManager implements NotificationListener { } } }; - message.setFrom(new InternetAddress("juick@juick.com")); + String fromAddress = StringUtils.isNotEmpty(from) ? from : "juick@juick.com"; + message.setFrom(fromAddress); message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to)); message.setSubject(subject); MimeBodyPart textBodyPart = new MimeBodyPart(); @@ -161,8 +162,10 @@ public class EmailManager implements NotificationListener { message.saveChanges(); transport.connect(); transport.sendMessage(message, message.getAllRecipients()); + return true; } catch (MessagingException ex) { logger.error("mail exception", ex); + return false; } } diff --git a/src/main/java/com/juick/server/api/Service.java b/src/main/java/com/juick/server/api/Service.java index 2a86c5e7..f137f2f7 100644 --- a/src/main/java/com/juick/server/api/Service.java +++ b/src/main/java/com/juick/server/api/Service.java @@ -152,7 +152,7 @@ public class Service { String verificationCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); emailService.addVerificationCode(null, from, verificationCode); String signupUrl = String.format("Follow this link to create Juick account: https://juick.com/signup?type=email&hash=%s", verificationCode); - emailManager.sendEmail(from, "Juick registration", signupUrl, StringUtils.EMPTY, Collections.emptyMap()); + emailManager.sendEmail("noreply@juick.com", from, "Juick registration", signupUrl, StringUtils.EMPTY, Collections.emptyMap()); } } } else { diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 0db710c9..74a720d4 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -20,6 +20,8 @@ package com.juick.server.api; import com.juick.User; import com.juick.model.AnonymousUser; import com.juick.model.ApplicationStatus; +import com.juick.server.EmailManager; +import com.juick.server.util.HttpBadRequestException; import com.juick.server.util.HttpNotFoundException; import com.juick.server.util.HttpUtils; import com.juick.server.util.WebUtils; @@ -28,19 +30,31 @@ import com.juick.service.*; import com.juick.service.security.annotation.Visitor; import com.juick.service.security.entities.JuickUser; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.inject.Inject; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.springframework.http.ResponseEntity.ok; +import static org.springframework.http.ResponseEntity.status; + /** * @author ugnich */ @@ -53,6 +67,8 @@ public class Users { @Inject private CrosspostService crosspostService; @Inject + private TelegramService telegramService; + @Inject private EmailService emailService; @Inject private TagService tagService; @@ -62,6 +78,8 @@ public class Users { private ImagesService imagesService; @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") private String tmpDir; + @Inject + private EmailManager emailManager; @RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public String getAuthToken(@Visitor User visitor) { @@ -106,6 +124,63 @@ public class Users { me.getTagStats().addAll(tagService.getUserTagStats(me.getUid())); return (SecureUser)userService.getUserInfo(me); } + @PostMapping("/api/me") + public ResponseEntity updateMe(@Visitor User visitor, + @RequestParam(required = false) String password, + @RequestParam(value = "jid-del", required = false) String jidForDeletion, + @RequestParam(value = "email-add", required = false) String newEmail, + @RequestParam(value = "email-del", required = false) String emailForDeletion, + @RequestParam(value = "account-del", required = false) String accountToDelete) { + if (StringUtils.isNotEmpty(password)) { + if (!userService.updatePassword(visitor, password)) { + throw new HttpBadRequestException(); + } + } + if (StringUtils.isNotEmpty(jidForDeletion)) { + if (!userService.deleteJID(visitor.getUid(), jidForDeletion)) { + throw new HttpBadRequestException(); + } + } + if (StringUtils.isNotEmpty(newEmail)) { + if (!emailService.verifyAddressByCode(visitor.getUid(), newEmail)) { + String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); + if (emailService.addVerificationCode(visitor.getUid(), newEmail, authCode)) { + if (!emailManager.sendEmail("noreply@juick.com", newEmail, "Juick authorization link", + String.format("Follow link to attach this email to Juick account:\n" + + "http://juick.com/settings?page=auth-email&code=%s\n\n" + + "If you don't know, what this mean - just ignore this mail.\n", authCode), + StringUtils.EMPTY, Collections.emptyMap())) { + throw new HttpBadRequestException(); + }; + } + } + } + if (StringUtils.isNotEmpty(emailForDeletion)) { + if (!emailService.deleteEmail(visitor.getUid(), emailForDeletion)) { + throw new HttpBadRequestException(); + } + } + if (StringUtils.isNotEmpty(accountToDelete)) { + switch (accountToDelete) { + case "twitter": + crosspostService.deleteTwitterToken(visitor.getUid()); + break; + case "vk": + crosspostService.deleteVKUser(visitor.getUid()); + break; + case "durov": + telegramService.deleteTelegramUser(visitor.getUid()); + break; + } + } + return ResponseEntity.ok().build(); + } + @PostMapping("/api/me/subscribe") + public ResponseEntity subscribeMe(@Visitor User visitor, String email) { + // TODO: check status + emailService.setNotificationsEmail(visitor.getUid(), email); + return ResponseEntity.ok().build(); + } @PostMapping("/api/me/upload") public void updateInfo(@Visitor User visitor, @RequestParam MultipartFile avatar) throws IOException { @@ -190,7 +265,7 @@ public class Users { } return IOUtils.toByteArray(URI.create(webApp.getAvatarUrl(user))); } - class SecureUser extends User { + public class SecureUser extends User { public String getHash() { return getAuthHash(); } diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 688e6148..e113c44a 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -34,6 +34,7 @@ import com.juick.model.CommandResult; import com.juick.model.PrivateChats; import com.juick.model.TagStats; import com.juick.server.*; +import com.juick.server.api.Users; import com.juick.server.api.activity.Profile; import com.juick.server.api.activity.model.Context; import com.juick.server.api.activity.model.activities.*; @@ -254,6 +255,9 @@ public class ServerTests { @Inject private ApplicationEventPublisher applicationEventPublisher; + @Inject + private Users usersController; + private static User ugnich, freefd, juick; static String ugnichName, ugnichPassword, freefdName, freefdPassword, juickName, juickPassword; URI emptyUri = URI.create(StringUtils.EMPTY); @@ -1936,7 +1940,7 @@ public class ServerTests { assertThat(userService.getUserByName("ugnich").isVerified(), is(true)); } @Test - public void avatarUploadOverApi() throws Exception { + public void changeProfileOverApi() throws Exception { ClassPathResource defaultAvatar = new ClassPathResource("static/av-96.png"); String hash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(defaultAvatar.getInputStream())); assertThat(webApp.getAvatarUrl(userService.getUserByName(freefdName)), is(String.format("http://localhost:8080/av-96-%s.png", hash))); @@ -1950,7 +1954,38 @@ public class ServerTests { String newHash = DigestUtils.md5DigestAsHex(newAvatarData); URI newUri = Paths.get(imgDir, "ao", String.format("%d.png", freefd.getUid())).toUri(); assertThat(DigestUtils.md5DigestAsHex(IOUtils.toByteArray(newUri)), is(newHash)); + mockMvc.perform(post("/api/me") + .with(httpBasic(ugnichName, ugnichPassword)) + .param("password", "newPassword")) + .andExpect(status().isOk()); + mockMvc.perform(get("/api/me") + .with(httpBasic(ugnichName, ugnichPassword))) + .andExpect(status().isUnauthorized()); + mockMvc.perform(post("/api/me") + .with(httpBasic(ugnichName, "newPassword")) + .param("password", ugnichPassword)) + .andExpect(status().isOk()); + mockMvc.perform(get("/api/me") + .with(httpBasic(ugnichName, ugnichPassword))) + .andExpect(status().isOk()); + assertThat(usersController.getMe(ugnich).getJIDs().size(), is(0)); + jdbcTemplate.update("INSERT INTO jids(user_id, jid) VALUES(?, ?)", + ugnich.getUid(), "test@example.com"); + jdbcTemplate.update("INSERT INTO jids(user_id, jid) VALUES(?, ?)", + ugnich.getUid(), "test2@example.com"); + assertThat(usersController.getMe(ugnich).getJIDs().size(), is(2)); + mockMvc.perform(post("/api/me") + .with(httpBasic(ugnichName, ugnichPassword)) + .param("jid-del", "test@example.com")) + .andExpect(status().isOk()); + assertThat(usersController.getMe(ugnich).getJIDs().size(), is(1)); + mockMvc.perform(post("/api/me") + .with(httpBasic(ugnichName, ugnichPassword)) + .param("jid-del", "test2@example.com")) + .andExpect(status().isBadRequest()); + jdbcTemplate.execute("DELETE FROM jids"); } + @Test public void varyMvcResponse() throws Exception { mockMvc.perform(get("/")) -- cgit v1.2.3