continuous-integration/drone/pr Build is passing
Регистрация остаётся закрытой. Администраторы — MAIL_ADMINS (по умолчанию первый ящик); роль не в базе, укравший ящик себя админом не сделает. API /api/admin/mailboxes, сгенерированный пароль — один раз в ответе, в лог не пишется. Настройки → MAILBOXES (видно только администратору). Тесты: MailboxAdminControllerTest (5), всего 73. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
3.9 KiB
Java
101 lines
3.9 KiB
Java
package com.mailserver.service;
|
|
|
|
import com.mailserver.domain.Folder;
|
|
import com.mailserver.domain.User;
|
|
import com.mailserver.repository.FolderRepository;
|
|
import com.mailserver.repository.UserRepository;
|
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
import org.springframework.stereotype.Service;
|
|
import reactor.core.publisher.Flux;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
import java.time.Instant;
|
|
|
|
@Service
|
|
public class UserService {
|
|
|
|
private final UserRepository userRepository;
|
|
private final FolderRepository folderRepository;
|
|
private final BCryptPasswordEncoder passwordEncoder;
|
|
|
|
public UserService(UserRepository userRepository, FolderRepository folderRepository) {
|
|
this.userRepository = userRepository;
|
|
this.folderRepository = folderRepository;
|
|
this.passwordEncoder = new BCryptPasswordEncoder();
|
|
}
|
|
|
|
public Mono<User> register(String email, String password, String displayName) {
|
|
return userRepository.existsByEmail(email)
|
|
.flatMap(exists -> {
|
|
if (exists) {
|
|
return Mono.error(new IllegalArgumentException("Email already registered: " + email));
|
|
}
|
|
|
|
User user = new User();
|
|
user.setEmail(email);
|
|
user.setPassword(passwordEncoder.encode(password));
|
|
user.setDisplayName(displayName == null || displayName.isBlank() ? email.substring(0, email.indexOf('@')) : displayName.trim());
|
|
user.setCreatedAt(Instant.now());
|
|
return userRepository.save(user);
|
|
})
|
|
.flatMap(user -> {
|
|
// Создаём стандартные папки
|
|
return Flux.fromIterable(MailService.SYSTEM_FOLDERS)
|
|
.concatMap(name -> {
|
|
Folder folder = new Folder();
|
|
folder.setUserId(user.getId());
|
|
folder.setName(name);
|
|
folder.setSystem(true);
|
|
folder.setCreatedAt(Instant.now());
|
|
return folderRepository.save(folder);
|
|
})
|
|
.then(Mono.just(user));
|
|
});
|
|
}
|
|
|
|
public Mono<User> findByEmail(String email) {
|
|
return userRepository.findByEmail(email)
|
|
.switchIfEmpty(Mono.error(new IllegalArgumentException("User not found: " + email)));
|
|
}
|
|
|
|
/**
|
|
* Проверяет, совпадает ли пароль с хэшем, хранящимся у пользователя.
|
|
*/
|
|
public boolean checkPassword(User user, String rawPassword) {
|
|
return passwordEncoder.matches(rawPassword, user.getPassword());
|
|
}
|
|
|
|
/** New password, only with the current one. */
|
|
public Mono<Void> changePassword(User user, String currentPassword, String newPassword) {
|
|
if (!checkPassword(user, currentPassword)) {
|
|
return Mono.error(MailService.badRequest("Current password is wrong"));
|
|
}
|
|
user.setPassword(passwordEncoder.encode(newPassword));
|
|
return userRepository.save(user).then();
|
|
}
|
|
|
|
public Flux<User> all() {
|
|
return userRepository.findAll();
|
|
}
|
|
|
|
public Mono<User> byId(Long id) {
|
|
return userRepository.findById(id);
|
|
}
|
|
|
|
/** Administrator sets a new password (Settings → Mailboxes): no current password needed. */
|
|
public Mono<User> setPassword(User user, String newPassword) {
|
|
user.setPassword(passwordEncoder.encode(newPassword));
|
|
return userRepository.save(user);
|
|
}
|
|
|
|
public Mono<User> setActive(User user, boolean active) {
|
|
user.setActive(active);
|
|
return userRepository.save(user);
|
|
}
|
|
|
|
public Mono<User> updateDisplayName(User user, String displayName) {
|
|
user.setDisplayName(displayName.trim());
|
|
return userRepository.save(user);
|
|
}
|
|
}
|