保存中...
@@ -70,6 +74,7 @@ export default {
publishMode: 'DIRECT',
passwordStrength: 'LOW',
aiFormatLimit: 3,
+ registerMode: 'DIRECT',
isLoadingPage: false,
isSaving: false
}
@@ -122,6 +127,12 @@ export default {
{ id: -1, name: '无限' }
])
},
+ fetchRegisterModes() {
+ return Promise.resolve([
+ { id: 'DIRECT', name: '直接注册', icon: 'fas fa-user-check' },
+ { id: 'WHITELIST', name: '白名单邀请制', icon: 'fas fa-envelope' }
+ ])
+ },
async loadAdminConfig() {
try {
const token = getToken()
@@ -133,6 +144,7 @@ export default {
this.publishMode = data.publishMode
this.passwordStrength = data.passwordStrength
this.aiFormatLimit = data.aiFormatLimit
+ this.registerMode = data.registerMode
}
} catch (e) {
// ignore
@@ -183,7 +195,7 @@ export default {
await fetch(`${API_BASE_URL}/api/admin/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
- body: JSON.stringify({ publishMode: this.publishMode, passwordStrength: this.passwordStrength, aiFormatLimit: this.aiFormatLimit })
+ body: JSON.stringify({ publishMode: this.publishMode, passwordStrength: this.passwordStrength, aiFormatLimit: this.aiFormatLimit, registerMode: this.registerMode })
})
}
toast.success('保存成功')
diff --git a/open-isle-cli/src/views/SignupPageView.vue b/open-isle-cli/src/views/SignupPageView.vue
index 04f0c31b2..cee7c74b5 100644
--- a/open-isle-cli/src/views/SignupPageView.vue
+++ b/open-isle-cli/src/views/SignupPageView.vue
@@ -94,6 +94,7 @@ export default {
email: '',
username: '',
password: '',
+ registerMode: 'DIRECT',
emailError: '',
usernameError: '',
passwordError: '',
@@ -103,6 +104,19 @@ export default {
isWaitingForEmailVerified: false
}
},
+ async mounted() {
+ try {
+ const res = await fetch(`${API_BASE_URL}/api/config`)
+ if (res.ok) {
+ const data = await res.json()
+ this.registerMode = data.registerMode
+ }
+ } catch {}
+ if (this.$route.query.verify) {
+ this.emailStep = 1
+ this.username = sessionStorage.getItem('signup_username') || ''
+ }
+ },
methods: {
clearErrors() {
this.emailError = ''
@@ -126,6 +140,13 @@ export default {
if (this.emailError || this.passwordError || this.usernameError) {
return
}
+ if (this.registerMode === 'WHITELIST') {
+ sessionStorage.setItem('signup_username', this.username)
+ sessionStorage.setItem('signup_email', this.email)
+ sessionStorage.setItem('signup_password', this.password)
+ this.$router.push('/signup-reason')
+ return
+ }
try {
console.log('base url: ', API_BASE_URL)
this.isWaitingForEmailSent = true
@@ -135,7 +156,7 @@ export default {
body: JSON.stringify({
username: this.username,
email: this.email,
- password: this.password,
+ password: this.password
})
})
this.isWaitingForEmailSent = false
@@ -175,9 +196,13 @@ export default {
}
},
signupWithGoogle() {
- googleSignIn(() => {
- this.$router.push('/')
- })
+ if (this.registerMode === 'WHITELIST') {
+ this.$router.push('/signup-reason?google=1')
+ } else {
+ googleSignIn(() => {
+ this.$router.push('/')
+ })
+ }
}
}
}
diff --git a/open-isle-cli/src/views/SignupReasonPageView.vue b/open-isle-cli/src/views/SignupReasonPageView.vue
new file mode 100644
index 000000000..a78a74db5
--- /dev/null
+++ b/open-isle-cli/src/views/SignupReasonPageView.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
diff --git a/src/main/java/com/openisle/controller/AdminConfigController.java b/src/main/java/com/openisle/controller/AdminConfigController.java
index b874a1a8d..97fd4744d 100644
--- a/src/main/java/com/openisle/controller/AdminConfigController.java
+++ b/src/main/java/com/openisle/controller/AdminConfigController.java
@@ -5,6 +5,8 @@ import com.openisle.model.PublishMode;
import com.openisle.service.PasswordValidator;
import com.openisle.service.PostService;
import com.openisle.service.AiUsageService;
+import com.openisle.service.RegisterModeService;
+import com.openisle.model.RegisterMode;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -16,6 +18,7 @@ public class AdminConfigController {
private final PostService postService;
private final PasswordValidator passwordValidator;
private final AiUsageService aiUsageService;
+ private final RegisterModeService registerModeService;
@GetMapping
public ConfigDto getConfig() {
@@ -23,6 +26,7 @@ public class AdminConfigController {
dto.setPublishMode(postService.getPublishMode());
dto.setPasswordStrength(passwordValidator.getStrength());
dto.setAiFormatLimit(aiUsageService.getFormatLimit());
+ dto.setRegisterMode(registerModeService.getRegisterMode());
return dto;
}
@@ -37,6 +41,9 @@ public class AdminConfigController {
if (dto.getAiFormatLimit() != null) {
aiUsageService.setFormatLimit(dto.getAiFormatLimit());
}
+ if (dto.getRegisterMode() != null) {
+ registerModeService.setRegisterMode(dto.getRegisterMode());
+ }
return getConfig();
}
@@ -45,5 +52,6 @@ public class AdminConfigController {
private PublishMode publishMode;
private PasswordStrength passwordStrength;
private Integer aiFormatLimit;
+ private RegisterMode registerMode;
}
}
diff --git a/src/main/java/com/openisle/controller/AdminUserController.java b/src/main/java/com/openisle/controller/AdminUserController.java
new file mode 100644
index 000000000..6fa878718
--- /dev/null
+++ b/src/main/java/com/openisle/controller/AdminUserController.java
@@ -0,0 +1,36 @@
+package com.openisle.controller;
+
+import com.openisle.model.User;
+import com.openisle.service.EmailSender;
+import com.openisle.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/admin/users")
+@RequiredArgsConstructor
+public class AdminUserController {
+ private final UserRepository userRepository;
+ private final EmailSender emailSender;
+
+ @PostMapping("/{id}/approve")
+ public ResponseEntity> approve(@PathVariable Long id) {
+ User user = userRepository.findById(id).orElseThrow();
+ user.setApproved(true);
+ userRepository.save(user);
+ emailSender.sendEmail(user.getEmail(), "Registration Approved",
+ "Your account has been approved. Visit: https://www.open-isle.com");
+ return ResponseEntity.ok().build();
+ }
+
+ @PostMapping("/{id}/reject")
+ public ResponseEntity> reject(@PathVariable Long id) {
+ User user = userRepository.findById(id).orElseThrow();
+ user.setApproved(false);
+ userRepository.save(user);
+ emailSender.sendEmail(user.getEmail(), "Registration Rejected",
+ "Your account request was rejected. Visit: https://www.open-isle.com");
+ return ResponseEntity.ok().build();
+ }
+}
diff --git a/src/main/java/com/openisle/controller/AuthController.java b/src/main/java/com/openisle/controller/AuthController.java
index c3abd2ae8..3aba770c9 100644
--- a/src/main/java/com/openisle/controller/AuthController.java
+++ b/src/main/java/com/openisle/controller/AuthController.java
@@ -6,6 +6,11 @@ import com.openisle.service.JwtService;
import com.openisle.service.UserService;
import com.openisle.service.CaptchaService;
import com.openisle.service.GoogleAuthService;
+import com.openisle.service.RegisterModeService;
+import com.openisle.service.NotificationService;
+import com.openisle.model.RegisterMode;
+import com.openisle.model.NotificationType;
+import com.openisle.repository.UserRepository;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
@@ -23,6 +28,9 @@ public class AuthController {
private final EmailSender emailService;
private final CaptchaService captchaService;
private final GoogleAuthService googleAuthService;
+ private final RegisterModeService registerModeService;
+ private final NotificationService notificationService;
+ private final UserRepository userRepository;
@Value("${app.captcha.enabled:false}")
private boolean captchaEnabled;
@@ -38,8 +46,15 @@ public class AuthController {
if (captchaEnabled && registerCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
return ResponseEntity.badRequest().body(Map.of("error", "Invalid captcha"));
}
- User user = userService.register(req.getUsername(), req.getEmail(), req.getPassword());
+ User user = userService.register(
+ req.getUsername(), req.getEmail(), req.getPassword(), req.getReason(), registerModeService.getRegisterMode());
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
+ if (!user.isApproved()) {
+ for (User admin : userRepository.findByRole(com.openisle.model.Role.ADMIN)) {
+ notificationService.createNotification(admin, NotificationType.REGISTER_REQUEST, null, null,
+ null, user, null, user.getRegisterReason());
+ }
+ }
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
}
@@ -67,8 +82,11 @@ public class AuthController {
@PostMapping("/google")
public ResponseEntity> loginWithGoogle(@RequestBody GoogleLoginRequest req) {
- Optional
user = googleAuthService.authenticate(req.getIdToken());
+ Optional user = googleAuthService.authenticate(req.getIdToken(), req.getReason(), registerModeService.getRegisterMode());
if (user.isPresent()) {
+ if (!user.get().isApproved()) {
+ return ResponseEntity.badRequest().body(Map.of("error", "Account awaiting approval"));
+ }
return ResponseEntity.ok(Map.of("token", jwtService.generateToken(user.get().getUsername())));
}
return ResponseEntity.badRequest().body(Map.of("error", "Invalid google token"));
@@ -85,6 +103,7 @@ public class AuthController {
private String email;
private String password;
private String captcha;
+ private String reason;
}
@Data
@@ -97,6 +116,7 @@ public class AuthController {
@Data
private static class GoogleLoginRequest {
private String idToken;
+ private String reason;
}
@Data
diff --git a/src/main/java/com/openisle/controller/ConfigController.java b/src/main/java/com/openisle/controller/ConfigController.java
index d2164a2bb..a754877eb 100644
--- a/src/main/java/com/openisle/controller/ConfigController.java
+++ b/src/main/java/com/openisle/controller/ConfigController.java
@@ -2,12 +2,15 @@ package com.openisle.controller;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
+import com.openisle.service.RegisterModeService;
+import com.openisle.model.RegisterMode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
+@lombok.RequiredArgsConstructor
public class ConfigController {
@Value("${app.captcha.enabled:false}")
@@ -28,6 +31,8 @@ public class ConfigController {
@Value("${app.ai.format-limit:3}")
private int aiFormatLimit;
+ private final RegisterModeService registerModeService;
+
@GetMapping("/config")
public ConfigResponse getConfig() {
ConfigResponse resp = new ConfigResponse();
@@ -37,6 +42,7 @@ public class ConfigController {
resp.setPostCaptchaEnabled(postCaptchaEnabled);
resp.setCommentCaptchaEnabled(commentCaptchaEnabled);
resp.setAiFormatLimit(aiFormatLimit);
+ resp.setRegisterMode(registerModeService.getRegisterMode());
return resp;
}
@@ -48,5 +54,6 @@ public class ConfigController {
private boolean postCaptchaEnabled;
private boolean commentCaptchaEnabled;
private int aiFormatLimit;
+ private RegisterMode registerMode;
}
}
diff --git a/src/main/java/com/openisle/controller/NotificationController.java b/src/main/java/com/openisle/controller/NotificationController.java
index a4e1db326..2e23fe452 100644
--- a/src/main/java/com/openisle/controller/NotificationController.java
+++ b/src/main/java/com/openisle/controller/NotificationController.java
@@ -64,6 +64,7 @@ public class NotificationController {
dto.setReactionType(n.getReactionType());
}
dto.setApproved(n.getApproved());
+ dto.setContent(n.getContent());
dto.setRead(n.isRead());
dto.setCreatedAt(n.getCreatedAt());
return dto;
@@ -107,6 +108,7 @@ public class NotificationController {
private CommentDto parentComment;
private AuthorDto fromUser;
private ReactionType reactionType;
+ private String content;
private Boolean approved;
private boolean read;
private LocalDateTime createdAt;
diff --git a/src/main/java/com/openisle/model/Notification.java b/src/main/java/com/openisle/model/Notification.java
index fa1f2f6c3..202bf2e30 100644
--- a/src/main/java/com/openisle/model/Notification.java
+++ b/src/main/java/com/openisle/model/Notification.java
@@ -45,6 +45,9 @@ public class Notification {
@Column(name = "reaction_type")
private ReactionType reactionType;
+ @Column(length = 1000)
+ private String content;
+
@Column
private Boolean approved;
diff --git a/src/main/java/com/openisle/model/NotificationType.java b/src/main/java/com/openisle/model/NotificationType.java
index e68dfca9c..a08e3a286 100644
--- a/src/main/java/com/openisle/model/NotificationType.java
+++ b/src/main/java/com/openisle/model/NotificationType.java
@@ -27,5 +27,7 @@ public enum NotificationType {
/** Someone unfollowed you */
USER_UNFOLLOWED,
/** A user you subscribe to created a post or comment */
- USER_ACTIVITY
+ USER_ACTIVITY,
+ /** A user requested registration approval */
+ REGISTER_REQUEST
}
diff --git a/src/main/java/com/openisle/model/RegisterMode.java b/src/main/java/com/openisle/model/RegisterMode.java
new file mode 100644
index 000000000..d8c8fd2e4
--- /dev/null
+++ b/src/main/java/com/openisle/model/RegisterMode.java
@@ -0,0 +1,9 @@
+package com.openisle.model;
+
+/**
+ * Application-wide user registration mode.
+ */
+public enum RegisterMode {
+ DIRECT,
+ WHITELIST
+}
diff --git a/src/main/java/com/openisle/model/User.java b/src/main/java/com/openisle/model/User.java
index 0d51ada45..dcc8516bb 100644
--- a/src/main/java/com/openisle/model/User.java
+++ b/src/main/java/com/openisle/model/User.java
@@ -42,6 +42,12 @@ public class User {
@Column(length = 1000)
private String introduction;
+ @Column(length = 1000)
+ private String registerReason;
+
+ @Column(nullable = false)
+ private boolean approved = true;
+
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role = Role.USER;
diff --git a/src/main/java/com/openisle/service/CommentService.java b/src/main/java/com/openisle/service/CommentService.java
index 77780a630..0fc74d272 100644
--- a/src/main/java/com/openisle/service/CommentService.java
+++ b/src/main/java/com/openisle/service/CommentService.java
@@ -43,16 +43,16 @@ public class CommentService {
comment.setContent(content);
comment = commentRepository.save(comment);
if (!author.getId().equals(post.getAuthor().getId())) {
- notificationService.createNotification(post.getAuthor(), NotificationType.COMMENT_REPLY, post, comment, null);
+ notificationService.createNotification(post.getAuthor(), NotificationType.COMMENT_REPLY, post, comment, null, null, null, null);
}
for (User u : subscriptionService.getPostSubscribers(postId)) {
if (!u.getId().equals(author.getId())) {
- notificationService.createNotification(u, NotificationType.POST_UPDATED, post, comment, null);
+ notificationService.createNotification(u, NotificationType.POST_UPDATED, post, comment, null, null, null, null);
}
}
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
if (!u.getId().equals(author.getId())) {
- notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, comment, null);
+ notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, comment, null, null, null, null);
}
}
return comment;
@@ -70,21 +70,21 @@ public class CommentService {
comment.setContent(content);
comment = commentRepository.save(comment);
if (!author.getId().equals(parent.getAuthor().getId())) {
- notificationService.createNotification(parent.getAuthor(), NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
+ notificationService.createNotification(parent.getAuthor(), NotificationType.COMMENT_REPLY, parent.getPost(), comment, null, null, null, null);
}
for (User u : subscriptionService.getCommentSubscribers(parentId)) {
if (!u.getId().equals(author.getId())) {
- notificationService.createNotification(u, NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
+ notificationService.createNotification(u, NotificationType.COMMENT_REPLY, parent.getPost(), comment, null, null, null, null);
}
}
for (User u : subscriptionService.getPostSubscribers(parent.getPost().getId())) {
if (!u.getId().equals(author.getId())) {
- notificationService.createNotification(u, NotificationType.POST_UPDATED, parent.getPost(), comment, null);
+ notificationService.createNotification(u, NotificationType.POST_UPDATED, parent.getPost(), comment, null, null, null, null);
}
}
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
if (!u.getId().equals(author.getId())) {
- notificationService.createNotification(u, NotificationType.USER_ACTIVITY, parent.getPost(), comment, null);
+ notificationService.createNotification(u, NotificationType.USER_ACTIVITY, parent.getPost(), comment, null, null, null, null);
}
}
return comment;
diff --git a/src/main/java/com/openisle/service/GoogleAuthService.java b/src/main/java/com/openisle/service/GoogleAuthService.java
index 8ae4d24a6..0cdf80953 100644
--- a/src/main/java/com/openisle/service/GoogleAuthService.java
+++ b/src/main/java/com/openisle/service/GoogleAuthService.java
@@ -23,7 +23,7 @@ public class GoogleAuthService {
@Value("${google.client-id:}")
private String clientId;
- public Optional authenticate(String idTokenString) {
+ public Optional authenticate(String idTokenString, String reason, com.openisle.model.RegisterMode mode) {
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
.setAudience(Collections.singletonList(clientId))
.build();
@@ -35,13 +35,13 @@ public class GoogleAuthService {
GoogleIdToken.Payload payload = idToken.getPayload();
String email = payload.getEmail();
String name = (String) payload.get("name");
- return Optional.of(processUser(email, name));
+ return Optional.of(processUser(email, name, reason, mode));
} catch (Exception e) {
return Optional.empty();
}
}
- private User processUser(String email, String name) {
+ private User processUser(String email, String name, String reason, com.openisle.model.RegisterMode mode) {
Optional existing = userRepository.findByEmail(email);
if (existing.isPresent()) {
User user = existing.get();
@@ -64,6 +64,8 @@ public class GoogleAuthService {
user.setPassword("");
user.setRole(Role.USER);
user.setVerified(true);
+ user.setRegisterReason(reason);
+ user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
user.setAvatar("https://github.com/identicons/" + username + ".png");
return userRepository.save(user);
}
diff --git a/src/main/java/com/openisle/service/NotificationService.java b/src/main/java/com/openisle/service/NotificationService.java
index 1859ea2f0..93f50859a 100644
--- a/src/main/java/com/openisle/service/NotificationService.java
+++ b/src/main/java/com/openisle/service/NotificationService.java
@@ -16,11 +16,11 @@ public class NotificationService {
private final UserRepository userRepository;
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved) {
- return createNotification(user, type, post, comment, approved, null, null);
+ return createNotification(user, type, post, comment, approved, null, null, null);
}
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved,
- User fromUser, ReactionType reactionType) {
+ User fromUser, ReactionType reactionType, String content) {
Notification n = new Notification();
n.setUser(user);
n.setType(type);
@@ -29,6 +29,7 @@ public class NotificationService {
n.setApproved(approved);
n.setFromUser(fromUser);
n.setReactionType(reactionType);
+ n.setContent(content);
return notificationRepository.save(n);
}
diff --git a/src/main/java/com/openisle/service/PostService.java b/src/main/java/com/openisle/service/PostService.java
index adb2bb8ea..a4f659d21 100644
--- a/src/main/java/com/openisle/service/PostService.java
+++ b/src/main/java/com/openisle/service/PostService.java
@@ -109,10 +109,10 @@ public class PostService {
java.util.List admins = userRepository.findByRole(com.openisle.model.Role.ADMIN);
for (User admin : admins) {
notificationService.createNotification(admin,
- NotificationType.POST_REVIEW_REQUEST, post, null, null, author, null);
+ NotificationType.POST_REVIEW_REQUEST, post, null, null, author, null, null);
}
notificationService.createNotification(author,
- NotificationType.POST_REVIEW_REQUEST, post, null, null, null, null);
+ NotificationType.POST_REVIEW_REQUEST, post, null, null, null, null, null);
}
// notify followers of author
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
@@ -124,6 +124,7 @@ public class PostService {
null,
null,
author,
+ null,
null);
}
}
@@ -151,9 +152,9 @@ public class PostService {
if (viewer != null && !viewer.equals(post.getAuthor().getUsername())) {
User viewerUser = userRepository.findByUsername(viewer).orElse(null);
if (viewerUser != null) {
- notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, viewerUser, null);
+ notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, viewerUser, null, null);
} else {
- notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null);
+ notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, null, null, null);
}
}
return post;
@@ -321,7 +322,7 @@ public class PostService {
}
post.setStatus(PostStatus.PUBLISHED);
post = postRepository.save(post);
- notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, true);
+ notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, true, null, null, null);
return post;
}
@@ -341,7 +342,7 @@ public class PostService {
}
post.setStatus(PostStatus.REJECTED);
post = postRepository.save(post);
- notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, false);
+ notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, false, null, null, null);
return post;
}
diff --git a/src/main/java/com/openisle/service/ReactionService.java b/src/main/java/com/openisle/service/ReactionService.java
index 95ba9af99..371028853 100644
--- a/src/main/java/com/openisle/service/ReactionService.java
+++ b/src/main/java/com/openisle/service/ReactionService.java
@@ -40,7 +40,7 @@ public class ReactionService {
reaction.setType(type);
reaction = reactionRepository.save(reaction);
if (!user.getId().equals(post.getAuthor().getId())) {
- notificationService.createNotification(post.getAuthor(), NotificationType.REACTION, post, null, null, user, type);
+ notificationService.createNotification(post.getAuthor(), NotificationType.REACTION, post, null, null, user, type, null);
}
return reaction;
}
@@ -63,7 +63,7 @@ public class ReactionService {
reaction.setType(type);
reaction = reactionRepository.save(reaction);
if (!user.getId().equals(comment.getAuthor().getId())) {
- notificationService.createNotification(comment.getAuthor(), NotificationType.REACTION, comment.getPost(), comment, null, user, type);
+ notificationService.createNotification(comment.getAuthor(), NotificationType.REACTION, comment.getPost(), comment, null, user, type, null);
}
return reaction;
}
diff --git a/src/main/java/com/openisle/service/RegisterModeService.java b/src/main/java/com/openisle/service/RegisterModeService.java
new file mode 100644
index 000000000..f12447a00
--- /dev/null
+++ b/src/main/java/com/openisle/service/RegisterModeService.java
@@ -0,0 +1,25 @@
+package com.openisle.service;
+
+import com.openisle.model.RegisterMode;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * Holds current registration mode. Configurable at runtime.
+ */
+@Service
+public class RegisterModeService {
+ private RegisterMode registerMode;
+
+ public RegisterModeService(@Value("${app.register.mode:WHITELIST}") RegisterMode registerMode) {
+ this.registerMode = registerMode;
+ }
+
+ public RegisterMode getRegisterMode() {
+ return registerMode;
+ }
+
+ public void setRegisterMode(RegisterMode mode) {
+ this.registerMode = mode;
+ }
+}
diff --git a/src/main/java/com/openisle/service/SubscriptionService.java b/src/main/java/com/openisle/service/SubscriptionService.java
index 959f11777..8f31a78c5 100644
--- a/src/main/java/com/openisle/service/SubscriptionService.java
+++ b/src/main/java/com/openisle/service/SubscriptionService.java
@@ -28,7 +28,7 @@ public class SubscriptionService {
ps.setPost(post);
if (!user.getId().equals(post.getAuthor().getId())) {
notificationService.createNotification(post.getAuthor(),
- NotificationType.POST_SUBSCRIBED, post, null, null, user, null);
+ NotificationType.POST_SUBSCRIBED, post, null, null, user, null, null);
}
return postSubRepo.save(ps);
});
@@ -41,7 +41,7 @@ public class SubscriptionService {
postSubRepo.delete(ps);
if (!user.getId().equals(post.getAuthor().getId())) {
notificationService.createNotification(post.getAuthor(),
- NotificationType.POST_UNSUBSCRIBED, post, null, null, user, null);
+ NotificationType.POST_UNSUBSCRIBED, post, null, null, user, null, null);
}
});
}
@@ -72,7 +72,7 @@ public class SubscriptionService {
us.setSubscriber(subscriber);
us.setTarget(target);
notificationService.createNotification(target,
- NotificationType.USER_FOLLOWED, null, null, null, subscriber, null);
+ NotificationType.USER_FOLLOWED, null, null, null, subscriber, null, null);
return userSubRepo.save(us);
});
}
@@ -83,7 +83,7 @@ public class SubscriptionService {
userSubRepo.findBySubscriberAndTarget(subscriber, target).ifPresent(us -> {
userSubRepo.delete(us);
notificationService.createNotification(target,
- NotificationType.USER_UNFOLLOWED, null, null, null, subscriber, null);
+ NotificationType.USER_UNFOLLOWED, null, null, null, subscriber, null, null);
});
}
diff --git a/src/main/java/com/openisle/service/UserService.java b/src/main/java/com/openisle/service/UserService.java
index bcf241e64..1a83aceac 100644
--- a/src/main/java/com/openisle/service/UserService.java
+++ b/src/main/java/com/openisle/service/UserService.java
@@ -22,7 +22,7 @@ public class UserService {
private final UsernameValidator usernameValidator;
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
- public User register(String username, String email, String password) {
+ public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) {
usernameValidator.validate(username);
passwordValidator.validate(password);
// ── 先按用户名查 ──────────────────────────────────────────
@@ -36,6 +36,8 @@ public class UserService {
u.setEmail(email); // 若不允许改邮箱可去掉
u.setPassword(passwordEncoder.encode(password));
u.setVerificationCode(genCode());
+ u.setRegisterReason(reason);
+ u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
return userRepository.save(u);
}
@@ -50,6 +52,8 @@ public class UserService {
u.setUsername(username); // 若不允许改用户名可去掉
u.setPassword(passwordEncoder.encode(password));
u.setVerificationCode(genCode());
+ u.setRegisterReason(reason);
+ u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
return userRepository.save(u);
}
@@ -62,6 +66,8 @@ public class UserService {
user.setVerified(false);
user.setVerificationCode(genCode());
user.setAvatar("https://github.com/identicons/" + username + ".png");
+ user.setRegisterReason(reason);
+ user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
return userRepository.save(user);
}
@@ -84,6 +90,7 @@ public class UserService {
public Optional authenticate(String username, String password) {
return userRepository.findByUsername(username)
.filter(User::isVerified)
+ .filter(User::isApproved)
.filter(user -> passwordEncoder.matches(password, user.getPassword()));
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 32f55533f..7c2908883 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -13,6 +13,9 @@ app.password.strength=${PASSWORD_STRENGTH:LOW}
# Post publish mode: DIRECT or REVIEW
app.post.publish-mode=${POST_PUBLISH_MODE:DIRECT}
+# User register mode: DIRECT or WHITELIST
+app.register.mode=${REGISTER_MODE:WHITELIST}
+
# Image upload configuration
app.upload.check-type=${UPLOAD_CHECK_TYPE:true}
app.upload.max-size=${UPLOAD_MAX_SIZE:5242880}
diff --git a/src/test/java/com/openisle/controller/AuthControllerTest.java b/src/test/java/com/openisle/controller/AuthControllerTest.java
index 3a58fdb12..ffbfdd3d9 100644
--- a/src/test/java/com/openisle/controller/AuthControllerTest.java
+++ b/src/test/java/com/openisle/controller/AuthControllerTest.java
@@ -2,6 +2,7 @@ package com.openisle.controller;
import com.openisle.model.User;
import com.openisle.service.*;
+import com.openisle.model.RegisterMode;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
@@ -36,6 +37,8 @@ class AuthControllerTest {
private CaptchaService captchaService;
@MockBean
private GoogleAuthService googleAuthService;
+ @MockBean
+ private RegisterModeService registerModeService;
@Test
void registerSendsEmail() throws Exception {
@@ -43,11 +46,12 @@ class AuthControllerTest {
user.setEmail("a@b.com");
user.setUsername("u");
user.setVerificationCode("123456");
- Mockito.when(userService.register(eq("u"), eq("a@b.com"), eq("p"))).thenReturn(user);
+ Mockito.when(registerModeService.getRegisterMode()).thenReturn(RegisterMode.DIRECT);
+ Mockito.when(userService.register(eq("u"), eq("a@b.com"), eq("p"), any(), eq(RegisterMode.DIRECT))).thenReturn(user);
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
- .content("{\"username\":\"u\",\"email\":\"a@b.com\",\"password\":\"p\"}"))
+ .content("{\"username\":\"u\",\"email\":\"a@b.com\",\"password\":\"p\",\"reason\":\"test reason more than twenty\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").exists());
diff --git a/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java b/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java
index bffd99f38..b49704459 100644
--- a/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java
+++ b/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java
@@ -17,7 +17,8 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = "app.register.mode=DIRECT")
class ComplexFlowIntegrationTest {
@Autowired
@@ -33,7 +34,7 @@ class ComplexFlowIntegrationTest {
HttpHeaders h = new HttpHeaders();
h.setContentType(MediaType.APPLICATION_JSON);
rest.postForEntity("/api/auth/register", new HttpEntity<>(
- Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
+ Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
User u = users.findByUsername(username).orElseThrow();
if (u.getVerificationCode() != null) {
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
diff --git a/src/test/java/com/openisle/integration/PublishModeIntegrationTest.java b/src/test/java/com/openisle/integration/PublishModeIntegrationTest.java
index d7755f6c0..2c0f30fcf 100644
--- a/src/test/java/com/openisle/integration/PublishModeIntegrationTest.java
+++ b/src/test/java/com/openisle/integration/PublishModeIntegrationTest.java
@@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.*;
/** Integration tests for review publish mode. */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
- properties = "app.post.publish-mode=REVIEW")
+ properties = {"app.post.publish-mode=REVIEW","app.register.mode=DIRECT"})
class PublishModeIntegrationTest {
@Autowired
@@ -34,7 +34,7 @@ class PublishModeIntegrationTest {
HttpHeaders h = new HttpHeaders();
h.setContentType(MediaType.APPLICATION_JSON);
rest.postForEntity("/api/auth/register", new HttpEntity<>(
- Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
+ Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
User u = users.findByUsername(username).orElseThrow();
if (u.getVerificationCode() != null) {
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
diff --git a/src/test/java/com/openisle/integration/SearchIntegrationTest.java b/src/test/java/com/openisle/integration/SearchIntegrationTest.java
index 233d725d0..e06849bd3 100644
--- a/src/test/java/com/openisle/integration/SearchIntegrationTest.java
+++ b/src/test/java/com/openisle/integration/SearchIntegrationTest.java
@@ -16,7 +16,8 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = "app.register.mode=DIRECT")
class SearchIntegrationTest {
@Autowired
private TestRestTemplate rest;
@@ -29,7 +30,7 @@ class SearchIntegrationTest {
HttpHeaders h = new HttpHeaders();
h.setContentType(MediaType.APPLICATION_JSON);
rest.postForEntity("/api/auth/register", new HttpEntity<>(
- Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
+ Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
User u = users.findByUsername(username).orElseThrow();
if (u.getVerificationCode() != null) {
rest.postForEntity("/api/auth/verify", new HttpEntity<>(