/* * Copyright (C) 2008-2024, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick.www.controllers; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.stream.IntStream; import jakarta.inject.Inject; import com.juick.model.User; import com.juick.service.EmailService; import com.juick.service.StorageService; import com.juick.service.TagService; import com.juick.service.TelegramService; import com.juick.service.UserService; import com.juick.service.activities.UpdateUserEvent; import com.juick.util.HttpBadRequestException; import com.juick.util.HttpUtils; import com.juick.www.WebApp; import io.swagger.v3.oas.annotations.Parameter; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; /** * * @author Ugnich Anton */ @Controller public class Settings { private static final Logger logger = LoggerFactory.getLogger(Settings.class); @Inject private TagService tagService; @Inject private UserService userService; @Inject private EmailService emailService; @Inject private TelegramService telegramService; @Inject private StorageService storageService; @Inject private WebApp webApp; @Inject private ApplicationEventPublisher applicationEventPublisher; @GetMapping("/settings") protected String doGet( @Parameter(hidden = true) User visitor, Locale locale, @RequestParam(required = false, defaultValue = "main") String page, @RequestParam(required = false) String code, ModelMap model) { visitor.setAvatar(webApp.getAvatarWebPath(visitor)); List pages = Arrays.asList("main", "password", "about", "auth-email", "privacy"); if (!pages.contains(page)) { page = "main"; } model.addAttribute("title", ResourceBundle.getBundle("messages", locale).getString("link.settings")); model.addAttribute("visitor", visitor); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("auths", userService.getAuthCodes(visitor)); model.addAttribute("email_active", emailService.getNotificationsEmail(visitor.getUid())); model.addAttribute("emails", userService.getEmails(visitor)); model.addAttribute("jids", userService.getAllJIDs(visitor)); List hours = IntStream.rangeClosed(0, 23).boxed() .map(i -> StringUtils.leftPad(String.format("%d", i), 2, "0")).toList(); model.addAttribute("hours", hours); model.addAttribute("fbstatus", userService.getFbCrossPostStatus(visitor.getUid())); model.addAttribute("twitter_name", userService.getTwitterName(visitor.getUid())); model.addAttribute("telegram_name", userService.getTelegramName(visitor.getUid())); model.addAttribute("userinfo", userService.getUserInfo(visitor)); if (page.equals("auth-email")) { if (emailService.verifyAddressByCode(visitor.getUid(), code)) { model.addAttribute("result", "OK!"); } else { model.addAttribute("result", "Sorry, code unknown."); } } return String.format("views/settings_%s", page); } @PostMapping("/settings") protected String doPost( @Parameter(hidden = true) User visitor, HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false) MultipartFile newAvatar, ModelMap model) throws IOException { if (visitor.isAnonymous()) { throw new HttpBadRequestException(); } List pages = Arrays.asList("main", "password", "about", "email", "email-add", "email-del", "email-subscr", "auth-email", "privacy", "jid-del", "twitter-del", "telegram-del", "facebook-disable", "facebook-enable", "vk-del"); String page = request.getParameter("page"); if (StringUtils.isEmpty(page) || !pages.contains(page)) { throw new HttpBadRequestException(); } String result = StringUtils.EMPTY; switch (page) { case "password": if (userService.updatePassword(visitor, request.getParameter("password"))) { result = "

Password has been changed.

"; String hash = userService.getHashByUID(visitor.getUid()); Cookie c = new Cookie("hash", hash); c.setMaxAge(365 * 24 * 60 * 60); response.addCookie(c); } break; case "about": visitor.setFullName(request.getParameter("fullname")); visitor.setCountry(request.getParameter("country")); visitor.setUrl(request.getParameter("url")); visitor.setDescription(request.getParameter("descr")); try { String avatarTmpPath = HttpUtils .receiveMultiPartFile(newAvatar, storageService.getTemporaryDirectory()).getHost(); if (StringUtils.isNotEmpty(avatarTmpPath)) { storageService.saveAvatar(avatarTmpPath, visitor); } if (userService.updateUserInfo(visitor)) { result = String.format("

Your info is updated.

Back to blog.

", visitor.getName()); } applicationEventPublisher.publishEvent(new UpdateUserEvent(this, visitor)); } catch (Exception e) { result = "

" + e.getMessage() + ". Back.

"; } break; case "jid-del": // FIXME: stop using ugnich-csv in parameters String[] params = request.getParameter("delete").split(";", 2); boolean res = false; if (params[0].equals("xmpp")) { res = userService.deleteJID(visitor.getUid(), params[1]); } else if (params[0].equals("xmpp-unauth")) { res = userService.unauthJID(visitor.getUid(), params[1]); } if (res) { result = "

Deleted. Back.

"; } else { result = "

Error

"; } break; case "email-add": if (!emailService.isValidEmail(request.getParameter("account"))) { result = "

Invalid email. Back.

"; } else { if (!emailService.verifyAddressByCode(visitor.getUid(), request.getParameter("account"))) { String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); if (emailService.addVerificationCode(visitor.getUid(), request.getParameter("account"), authCode)) { Session session = Session.getDefaultInstance(System.getProperties()); try { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress("noreply@juick.com")); message.addRecipient(Message.RecipientType.TO, new InternetAddress(request.getParameter("account"))); message.setSubject("Juick authorization link"); message.setText(String.format("Follow link to attach this email to Juick account:\n" + "https://juick.com/settings?page=auth-email&code=%s\n\n" + "If you don't know, what this mean - just ignore this mail.\n", authCode)); Transport.send(message); result = "

Authorization link has been sent to your email. Follow it to proceed.

" + "

Back

"; } catch (MessagingException ex) { logger.error("mail exception", ex); throw new HttpBadRequestException(); } } } } break; case "email-del": if (emailService.deleteEmail(visitor.getUid(), request.getParameter("account"))) { result = "

Deleted. Back.

"; } else { result = "

An error occured while deleting.

"; } break; case "email-subscr": if (emailService.setNotificationsEmail(visitor.getUid(), request.getParameter("account"))) { result = String.format("

Saved! Will send notifications to %s." + "

Back

", request.getParameter("account")); } else { result = "

Disabled.

Back

"; } break; case "twitter-del": userService.deleteTwitterToken(visitor.getUid()); for (Cookie cookie : request.getCookies()) { if (cookie.getName().equals("request_token")) { cookie.setMaxAge(0); response.addCookie(cookie); } if (cookie.getName().equals("request_token_secret")) { cookie.setMaxAge(0); response.addCookie(cookie); } } result = "

Back

"; break; case "telegram-del": if (userService.canDeleteTelegramUser(visitor)) { telegramService.deleteTelegramUser(visitor.getUid()); } result = "

Back

"; break; case "facebook-disable": userService.disableFBCrosspost(visitor.getUid()); result = "

Back

"; break; case "facebook-enable": userService.enableFBCrosspost(visitor.getUid()); result = "

Back

"; break; case "vk-del": userService.deleteVKUser(visitor.getUid()); result = "

Back

"; break; default: throw new HttpBadRequestException(); } model.addAttribute("title", "Настройки"); model.addAttribute("visitor", visitor); model.addAttribute("result", result); return "views/settings_result"; } @PostMapping("/settings/unsubscribe") public String unsubscribeOneClick( User user, @RequestParam(name = "List-Unsubscribe") String unsubscribe, ModelMap model) { if (!user.isAnonymous()) { if (unsubscribe.equals("One-Click")) { emailService.setNotificationsEmail(user.getUid(), StringUtils.EMPTY); model.addAttribute("title", "Настройки"); model.addAttribute("visitor", user); model.addAttribute("result", "Unsubscribed"); return "views/settings_result"; } } throw new HttpBadRequestException(); } }