Преглед на файлове

Activation via telegram

Ryan Wright преди 1 месец
родител
ревизия
55a8175992

+ 15 - 0
pom.xml

@@ -24,6 +24,17 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.telegram</groupId>
+            <artifactId>telegrambots-springboot-longpolling-starter</artifactId>
+            <version>7.10.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.telegram</groupId>
+            <artifactId>telegrambots-client</artifactId>
+            <version>7.7.1</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-freemarker</artifactId>
@@ -56,6 +67,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 2 - 1
src/main/java/org/example/sweater/Application.java

@@ -1,12 +1,13 @@
 package org.example.sweater;
 
+import org.example.sweater.config.TelegramBotProperties;
 import org.example.sweater.config.UploadProperties;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 
 @SpringBootApplication
-@EnableConfigurationProperties(UploadProperties.class)
+@EnableConfigurationProperties({UploadProperties.class, TelegramBotProperties.class})
 public class Application {
     public static void main(String[] args) {
         SpringApplication.run(Application.class, args);

+ 26 - 0
src/main/java/org/example/sweater/config/TelegramBotProperties.java

@@ -0,0 +1,26 @@
+package org.example.sweater.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "telegram")
+public class TelegramBotProperties {
+    private String token;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    private String name;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+}

+ 1 - 1
src/main/java/org/example/sweater/config/WebSecurityConfig.java

@@ -49,7 +49,7 @@ public class WebSecurityConfig {
     ) throws Exception {
         http
                 .authorizeHttpRequests((requests) -> requests
-                    .requestMatchers("/", "/registration", "/static/**")
+                    .requestMatchers("/", "/registration", "/activation", "/static/**")
                     .permitAll()
                     .anyRequest()
                     .authenticated()

+ 23 - 16
src/main/java/org/example/sweater/controller/AuthorizationController.java

@@ -1,32 +1,43 @@
 package org.example.sweater.controller;
 
-import jakarta.servlet.http.HttpServletRequest;
-import org.example.sweater.domain.Role;
+import org.example.sweater.config.TelegramBotProperties;
 import org.example.sweater.domain.User;
-import org.example.sweater.repos.UsersRepository;
-import org.springframework.security.web.csrf.CsrfToken;
+import org.example.sweater.service.UserService;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 
-import java.util.Collections;
 import java.util.Map;
 
 @Controller
 public class AuthorizationController {
-    public AuthorizationController(UsersRepository usersRepository) {
-        this.usersRepository = usersRepository;
+    public AuthorizationController(UserService usersRepository, TelegramBotProperties telegramBotProperties) {
+        this.userService = usersRepository;
+        this.telegramProperties = telegramBotProperties;
     }
 
-    private final UsersRepository usersRepository;
+    private final UserService userService;
+    private final TelegramBotProperties telegramProperties;
 
     @GetMapping("/login")
     public String login() {
         return "login";
     }
 
+    @GetMapping("/activation")
+    public String activation(
+            String key,
+            Model model
+    ) {
+        String link = "https://t.me/" + telegramProperties.getName() + "?start=" + key;
+
+        model.addAttribute("link", link);
+        return "activation";
+    }
+
     @GetMapping("/registration")
     public String registration() {
         return "registration";
@@ -34,17 +45,13 @@ public class AuthorizationController {
 
     @PostMapping("/registration")
     public String addUser(User user, Map<String, Object> model) {
-        User userFromStorage = usersRepository.findByUsername(user.getUsername());
+        boolean isAdded = userService.addUser(user);
 
-        if (userFromStorage != null) {
+        if (!isAdded) {
             model.put("message", "User exists!");
             return "registration";
         }
 
-        user.setActive(true);
-        user.setRoles(Collections.singleton(Role.USER));
-        usersRepository.save(user);
-
-        return "redirect:/login";
+        return "redirect:/activation?key=" + user.getTelegramActivationCode();
     }
 }

+ 13 - 13
src/main/java/org/example/sweater/domain/Message.java

@@ -16,17 +16,17 @@ public class Message {
     private Long id;
 
     private String text;
-//    public void setText(String text) {
-//        this.text = text;
-//    }
+    public void setText(String text) {
+        this.text = text;
+    }
     public String getText() {
         return text;
     }
 
     private String tag;
-//    public void setTag(String tag) {
-//        this.tag = tag;
-//    }
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
     public String getTag() {
         return tag;
     }
@@ -39,13 +39,13 @@ public class Message {
         return author != null ? author.getUsername() : "<none>";
     }
 
-//    public User getAuthor() {
-//        return author;
-//    }
-//
-//    public void setAuthor(User author) {
-//        this.author = author;
-//    }
+    public User getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(User author) {
+        this.author = author;
+    }
 
     public Long getId() {
         return id;

+ 29 - 1
src/main/java/org/example/sweater/domain/User.java

@@ -5,7 +5,6 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
 
 @Entity
@@ -18,6 +17,35 @@ public class User implements UserDetails {
     private String password;
     private boolean active;
 
+    public String getTelegramUsername() {
+        return telegramUsername;
+    }
+
+    public void setTelegramUsername(String telegramUsername) {
+        this.telegramUsername = telegramUsername;
+    }
+
+    public String getTelegramChatID() {
+        return telegramChatID;
+    }
+
+    public void setTelegramChatID(String telegramChatID) {
+        this.telegramChatID = telegramChatID;
+    }
+
+    private String telegramUsername;
+    private String telegramChatID;
+
+    public String getTelegramActivationCode() {
+        return telegramActivationCode;
+    }
+
+    public void setTelegramActivationCode(String telegramActivationCode) {
+        this.telegramActivationCode = telegramActivationCode;
+    }
+
+    private String telegramActivationCode;
+
     @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
     @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
     @Enumerated(EnumType.STRING)

+ 1 - 0
src/main/java/org/example/sweater/repos/UsersRepository.java

@@ -5,4 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
 
 public interface UsersRepository extends JpaRepository<User, Long> {
     User findByUsername(String username);
+    User findByTelegramUsername(String telegramUsername);
 }

+ 0 - 1
src/main/java/org/example/sweater/service/FileService.java

@@ -6,7 +6,6 @@ import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Objects;
 import java.util.UUID;
 
 @Component

+ 121 - 0
src/main/java/org/example/sweater/service/TelegramAccountActivation.java

@@ -0,0 +1,121 @@
+package org.example.sweater.service;
+
+import org.example.sweater.config.TelegramBotProperties;
+import org.example.sweater.domain.User;
+import org.example.sweater.repos.UsersRepository;
+import org.springframework.stereotype.Component;
+import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
+import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer;
+import org.telegram.telegrambots.longpolling.starter.SpringLongPollingBot;
+import org.telegram.telegrambots.longpolling.util.LongPollingSingleThreadUpdateConsumer;
+import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
+import org.telegram.telegrambots.meta.api.objects.Update;
+import org.telegram.telegrambots.meta.api.objects.message.Message;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardRow;
+import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
+import org.telegram.telegrambots.meta.generics.TelegramClient;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+public class TelegramAccountActivation implements SpringLongPollingBot, LongPollingSingleThreadUpdateConsumer {
+    private final TelegramBotProperties properties;
+
+    private final TelegramClient telegramClient;
+    private final UsersRepository usersRepository;
+
+    public TelegramAccountActivation(TelegramBotProperties properties, UsersRepository usersRepository) {
+        this.properties = properties;
+        this.telegramClient = new OkHttpTelegramClient(properties.getToken());
+        this.usersRepository = usersRepository;
+    }
+
+    @Override
+    public String getBotToken() {
+        return properties.getToken();
+    }
+
+    @Override
+    public LongPollingUpdateConsumer getUpdatesConsumer() {
+        return this;
+    }
+
+    private User checkUserCode(String username, String uuid) {
+        User user = usersRepository.findByTelegramUsername(username);
+        if (user == null || !user.getTelegramActivationCode().equals(uuid)) {
+            return null;
+        }
+
+        user.setActive(true);
+        user.setTelegramActivationCode(null);
+        usersRepository.save(user);
+        return user;
+    }
+
+    @Override
+    public void consume(Update update) {
+        if (!update.hasMessage() || !update.getMessage().hasText()) {
+            return;
+        }
+
+        Message message = update.getMessage();
+        String text = message.getText();
+        String startCommand = "/start";
+
+        if (!text.contains(startCommand)) {
+            return;
+        }
+
+        String username = message.getFrom().getUserName();
+        Long chatId = message.getChatId();
+        Pattern uuidInMessagePattern = Pattern.compile("[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", Pattern.CASE_INSENSITIVE);
+        Matcher matcher = uuidInMessagePattern.matcher(text);
+
+        if (!matcher.find()) {
+            try {
+                SendMessage answer = SendMessage.builder().chatId(chatId).text("Unexpected message format").build();
+                telegramClient.execute(answer);
+            } catch (TelegramApiException e) {
+                System.out.println("TelegramApiException: " + e.getMessage());
+            }
+            return;
+        }
+
+        int uuidStartIndex = matcher.start();
+        int uuidEndIndex = matcher.end();
+        String uuid = text.substring(uuidStartIndex, uuidEndIndex);
+        User user = checkUserCode(username, uuid);
+        try {
+            SendMessage answer;
+
+            if (user != null) {
+                InlineKeyboardButton goLoginButton = InlineKeyboardButton
+                        .builder()
+                        .text("Go login")
+//                        .url("http://localhost:8080/login")
+                        .url("https://registry.thesol.ru")
+                        .build();
+                InlineKeyboardMarkup keyboard = InlineKeyboardMarkup
+                        .builder()
+                        .keyboardRow(new InlineKeyboardRow(goLoginButton))
+                        .build();
+
+                answer = SendMessage
+                        .builder()
+                        .text("User " + username + " was activated!")
+                        .replyMarkup(keyboard)
+                        .chatId(chatId)
+                        .build();
+            } else {
+                answer = SendMessage.builder().text("Activation failed").chatId(chatId).build();
+            }
+
+            telegramClient.execute(answer);
+        } catch (TelegramApiException e) {
+            System.out.println("TelegramApiException: " + e.getMessage());
+        }
+    }
+}

+ 36 - 1
src/main/java/org/example/sweater/service/UserService.java

@@ -1,21 +1,56 @@
 package org.example.sweater.service;
 
+import org.example.sweater.config.TelegramBotProperties;
+import org.example.sweater.domain.Role;
+import org.example.sweater.domain.User;
 import org.example.sweater.repos.UsersRepository;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Service;
 
+import java.util.Collections;
+import java.util.UUID;
+
 @Service
 public class UserService implements UserDetailsService {
     private final UsersRepository usersRepository;
+    private final TelegramBotProperties telegramBotProperties;
 
-    public UserService(UsersRepository usersRepository) {
+    public UserService(UsersRepository usersRepository, TelegramBotProperties telegramBotProperties) {
         this.usersRepository = usersRepository;
+        this.telegramBotProperties = telegramBotProperties;
     }
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         return usersRepository.findByUsername(username);
     }
+
+    public boolean addUser(User user) {
+        User userFromStorage = usersRepository.findByUsername(user.getUsername());
+
+        if (userFromStorage != null) {
+            return false;
+        }
+
+        user.setActive(false);
+        user.setRoles(Collections.singleton(Role.USER));
+        user.setTelegramActivationCode(UUID.randomUUID().toString());
+        usersRepository.save(user);
+
+        String telegramUsername = user.getUsername();
+
+        if (telegramUsername == null || telegramUsername.isEmpty()) {
+            return false;
+        }
+
+        String message = String.format(
+                "Hello, %s! \n Welcome to Sweater. Please, visit next link to activate your account with username %s https://t.me/%s?start=%s",
+                user.getUsername(), user.getUsername(), telegramBotProperties.getName(), user.getTelegramActivationCode()
+        );
+        System.out.println(message);
+
+        return true;
+    }
 }

+ 2 - 0
src/main/resources/application.properties.example

@@ -4,3 +4,5 @@ spring.datasource.password=admin
 spring.jpa.generate-ddl=true
 spring.freemarker.expose-request-attributes=true
 upload.path=uploads
+telegram.token=xxx
+telegram.name=xxx

+ 12 - 0
src/main/resources/templates/activation.ftl

@@ -0,0 +1,12 @@
+<#import "parts/common.ftl" as common>
+
+<@common.page>
+    <div class="row">
+        <div class="col-12 text-center">
+            <h2 class="mb-3">Thank you for registration</h2>
+            <p class="mb-3">Now activate your telegram account</p>
+            <a href="${link}" class="btn btn-primary">Activate Telegram</a>
+        </div>
+    </div>
+
+</@common.page>

+ 7 - 7
src/main/resources/templates/feed.ftl

@@ -35,8 +35,8 @@
     </div>
 
     <div class="container mt-5 collapse" id="searchForms">
-        <div class="row justify-content-center">
-            <div class="col-md-6">
+        <div class="row">
+            <div class="col-12 col-sm-6">
                 <form method="get" action="/feed">
                     <div class="form-group mb-3">
                         <label for="search_text">Search by Message Text</label>
@@ -53,8 +53,8 @@
         </div>
     </div>
     <div class="container mt-5 collapse" id="createMessage">
-        <div class="row justify-content-center">
-            <div class="col-md-6">
+        <div class="row">
+            <div class="col-12 col-sm-6">
                 <form method="post" enctype="multipart/form-data">
                     <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
                     <div class="form-group mb-3">
@@ -62,7 +62,7 @@
                         <input type="text" class="form-control" id="messageText" name="text" placeholder="Enter message text" required>
                     </div>
                     <div class="form-group mb-3">
-                        <label for="category">Category</label>
+                        <label for="category">Tag</label>
                         <input type="text" class="form-control" id="category" name="tag" placeholder="Enter category" required>
                     </div>
                     <div class="form-group mb-3">
@@ -86,12 +86,12 @@
                             <img class="card-img-top" alt="Image" src="/img/${message.filename}" />
                         </#if>
                         <div class="card-body">
-                            <h5 class="card-subtitle mb-2 text-body-secondary">${message.authorName}</h5>
                             <p class="card-text">${message.text}</p>
+                            <i>${message.tag}</i>
                         </div>
                         <div class="card-footer">
                             <div class="d-flex justify-content-between align-items-center">
-                                <div>${message.tag}</div>
+                                <div>${message.authorName}</div>
                                 <form action="/feed/delete" method="post">
                                     <input type="hidden" value="${message.id}" name="message" />
                                     <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

+ 7 - 0
src/main/resources/templates/parts/login.ftl

@@ -9,6 +9,12 @@
                 </#if>
 
                 <form action="${action}" method="post">
+                    <#if isRegistration>
+                        <div class="form-group mb-3">
+                            <label for="tg">Telegram username</label>
+                            <input name="telegramUsername" id="tg" class="form-control" type="text" placeholder="telegram username" required />
+                        </div>
+                    </#if>
                     <div class="form-group mb-3">
                         <label for="username">Username</label>
                         <input name="username" class="form-control" id="username" type="text" required placeholder="Username" />
@@ -17,6 +23,7 @@
                         <label for="password">Password</label>
                         <input name="password" id="password" class="form-control" type="password" placeholder="Password" required />
                     </div>
+
                     <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
                     <div class="form-group mb-3">
                         <button type="submit" class="btn btn-primary">