mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-02 01:50:46 +08:00
feat: track oauth new-user result
This commit is contained in:
12
backend/src/main/java/com/openisle/service/AuthResult.java
Normal file
12
backend/src/main/java/com/openisle/service/AuthResult.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.model.User;
|
||||
import lombok.Value;
|
||||
|
||||
/** Result for OAuth authentication indicating whether a new user was created. */
|
||||
@Value
|
||||
public class AuthResult {
|
||||
User user;
|
||||
boolean newUser;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class DiscordAuthService {
|
||||
@Value("${discord.client-secret:}")
|
||||
private String clientSecret;
|
||||
|
||||
public Optional<User> authenticate(String code, com.openisle.model.RegisterMode mode, String redirectUri) {
|
||||
public Optional<AuthResult> authenticate(String code, com.openisle.model.RegisterMode mode, String redirectUri, boolean viaInvite) {
|
||||
try {
|
||||
String tokenUrl = "https://discord.com/api/oauth2/token";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -67,13 +67,13 @@ public class DiscordAuthService {
|
||||
if (email == null) {
|
||||
email = (username != null ? username : id) + "@users.noreply.discord.com";
|
||||
}
|
||||
return Optional.of(processUser(email, username, avatar, mode));
|
||||
return Optional.of(processUser(email, username, avatar, mode, viaInvite));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private User processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode) {
|
||||
private AuthResult processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode, boolean viaInvite) {
|
||||
Optional<User> existing = userRepository.findByEmail(email);
|
||||
if (existing.isPresent()) {
|
||||
User user = existing.get();
|
||||
@@ -82,7 +82,7 @@ public class DiscordAuthService {
|
||||
user.setVerificationCode(null);
|
||||
userRepository.save(user);
|
||||
}
|
||||
return user;
|
||||
return new AuthResult(user, false);
|
||||
}
|
||||
String baseUsername = username != null ? username : email.split("@")[0];
|
||||
String finalUsername = baseUsername;
|
||||
@@ -96,12 +96,12 @@ public class DiscordAuthService {
|
||||
user.setPassword("");
|
||||
user.setRole(Role.USER);
|
||||
user.setVerified(true);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT || viaInvite);
|
||||
if (avatar != null) {
|
||||
user.setAvatar(avatar);
|
||||
} else {
|
||||
user.setAvatar("https://cdn.discordapp.com/embed/avatars/0.png");
|
||||
}
|
||||
return userRepository.save(user);
|
||||
return new AuthResult(userRepository.save(user), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class GithubAuthService {
|
||||
@Value("${github.client-secret:}")
|
||||
private String clientSecret;
|
||||
|
||||
public Optional<User> authenticate(String code, com.openisle.model.RegisterMode mode, String redirectUri) {
|
||||
public Optional<AuthResult> authenticate(String code, com.openisle.model.RegisterMode mode, String redirectUri, boolean viaInvite) {
|
||||
try {
|
||||
String tokenUrl = "https://github.com/login/oauth/access_token";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -86,13 +86,13 @@ public class GithubAuthService {
|
||||
if (email == null) {
|
||||
email = username + "@users.noreply.github.com";
|
||||
}
|
||||
return Optional.of(processUser(email, username, avatarUrl, mode));
|
||||
return Optional.of(processUser(email, username, avatarUrl, mode, viaInvite));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private User processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode) {
|
||||
private AuthResult processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode, boolean viaInvite) {
|
||||
Optional<User> existing = userRepository.findByEmail(email);
|
||||
if (existing.isPresent()) {
|
||||
User user = existing.get();
|
||||
@@ -101,7 +101,7 @@ public class GithubAuthService {
|
||||
user.setVerificationCode(null);
|
||||
userRepository.save(user);
|
||||
}
|
||||
return user;
|
||||
return new AuthResult(user, false);
|
||||
}
|
||||
String baseUsername = username != null ? username : email.split("@")[0];
|
||||
String finalUsername = baseUsername;
|
||||
@@ -115,12 +115,12 @@ public class GithubAuthService {
|
||||
user.setPassword("");
|
||||
user.setRole(Role.USER);
|
||||
user.setVerified(true);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT || viaInvite);
|
||||
if (avatar != null) {
|
||||
user.setAvatar(avatar);
|
||||
} else {
|
||||
user.setAvatar(avatarGenerator.generate(finalUsername));
|
||||
}
|
||||
return userRepository.save(user);
|
||||
return new AuthResult(userRepository.save(user), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class GoogleAuthService {
|
||||
@Value("${google.client-id:}")
|
||||
private String clientId;
|
||||
|
||||
public Optional<User> authenticate(String idTokenString, com.openisle.model.RegisterMode mode) {
|
||||
public Optional<AuthResult> authenticate(String idTokenString, com.openisle.model.RegisterMode mode, boolean viaInvite) {
|
||||
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
|
||||
.setAudience(Collections.singletonList(clientId))
|
||||
.build();
|
||||
@@ -38,13 +38,13 @@ public class GoogleAuthService {
|
||||
String email = payload.getEmail();
|
||||
String name = (String) payload.get("name");
|
||||
String picture = (String) payload.get("picture");
|
||||
return Optional.of(processUser(email, name, picture, mode));
|
||||
return Optional.of(processUser(email, name, picture, mode, viaInvite));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private User processUser(String email, String name, String avatar, com.openisle.model.RegisterMode mode) {
|
||||
private AuthResult processUser(String email, String name, String avatar, com.openisle.model.RegisterMode mode, boolean viaInvite) {
|
||||
Optional<User> existing = userRepository.findByEmail(email);
|
||||
if (existing.isPresent()) {
|
||||
User user = existing.get();
|
||||
@@ -53,8 +53,7 @@ public class GoogleAuthService {
|
||||
user.setVerificationCode(null);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
return new AuthResult(user, false);
|
||||
}
|
||||
User user = new User();
|
||||
String baseUsername = email.split("@")[0];
|
||||
@@ -68,12 +67,12 @@ public class GoogleAuthService {
|
||||
user.setPassword("");
|
||||
user.setRole(Role.USER);
|
||||
user.setVerified(true);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT || viaInvite);
|
||||
if (avatar != null) {
|
||||
user.setAvatar(avatar);
|
||||
} else {
|
||||
user.setAvatar(avatarGenerator.generate(username));
|
||||
}
|
||||
return userRepository.save(user);
|
||||
return new AuthResult(userRepository.save(user), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.model.InviteToken;
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.repository.InviteTokenRepository;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InviteService {
|
||||
private final InviteTokenRepository inviteTokenRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final JwtService jwtService;
|
||||
private final PointService pointService;
|
||||
|
||||
public String generate(String username) {
|
||||
User inviter = userRepository.findByUsername(username).orElseThrow();
|
||||
LocalDate today = LocalDate.now();
|
||||
Optional<InviteToken> existing = inviteTokenRepository.findByInviterAndCreatedDate(inviter, today);
|
||||
if (existing.isPresent()) {
|
||||
return existing.get().getToken();
|
||||
}
|
||||
String token = jwtService.generateInviteToken(username);
|
||||
InviteToken inviteToken = new InviteToken();
|
||||
inviteToken.setToken(token);
|
||||
inviteToken.setInviter(inviter);
|
||||
inviteToken.setCreatedDate(today);
|
||||
inviteToken.setUsageCount(0);
|
||||
inviteTokenRepository.save(inviteToken);
|
||||
return token;
|
||||
}
|
||||
|
||||
public boolean validate(String token) {
|
||||
try {
|
||||
jwtService.validateAndGetSubjectForInvite(token);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
InviteToken invite = inviteTokenRepository.findById(token).orElse(null);
|
||||
return invite != null && invite.getUsageCount() < 3;
|
||||
}
|
||||
|
||||
public void consume(String token) {
|
||||
InviteToken invite = inviteTokenRepository.findById(token).orElseThrow();
|
||||
invite.setUsageCount(invite.getUsageCount() + 1);
|
||||
inviteTokenRepository.save(invite);
|
||||
pointService.awardForInvite(invite.getInviter().getUsername());
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ public class JwtService {
|
||||
@Value("${app.jwt.reset-secret}")
|
||||
private String resetSecret;
|
||||
|
||||
@Value("${app.jwt.invite-secret}")
|
||||
private String inviteSecret;
|
||||
|
||||
@Value("${app.jwt.expiration}")
|
||||
private long expiration;
|
||||
|
||||
@@ -70,6 +73,17 @@ public class JwtService {
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String generateInviteToken(String subject) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
return Jwts.builder()
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(getSigningKeyForSecret(inviteSecret))
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String validateAndGetSubject(String token) {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKeyForSecret(secret))
|
||||
@@ -96,4 +110,13 @@ public class JwtService {
|
||||
.getBody();
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
public String validateAndGetSubjectForInvite(String token) {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKeyForSecret(inviteSecret))
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
return claims.getSubject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ public class PointService {
|
||||
return addPoint(user, 30);
|
||||
}
|
||||
|
||||
public int awardForInvite(String userName) {
|
||||
User user = userRepository.findByUsername(userName).orElseThrow();
|
||||
return addPoint(user, 500);
|
||||
}
|
||||
|
||||
private PointLog getTodayLog(User user) {
|
||||
LocalDate today = LocalDate.now();
|
||||
return pointLogRepository.findByUserAndLogDate(user, today)
|
||||
|
||||
@@ -33,11 +33,12 @@ public class TwitterAuthService {
|
||||
@Value("${twitter.client-secret:}")
|
||||
private String clientSecret;
|
||||
|
||||
public Optional<User> authenticate(
|
||||
public Optional<AuthResult> authenticate(
|
||||
String code,
|
||||
String codeVerifier,
|
||||
RegisterMode mode,
|
||||
String redirectUri) {
|
||||
String redirectUri,
|
||||
boolean viaInvite) {
|
||||
|
||||
logger.debug("Starting authentication with code {} and verifier {}", code, codeVerifier);
|
||||
|
||||
@@ -106,10 +107,10 @@ public class TwitterAuthService {
|
||||
// Twitter v2 默认拿不到 email;如果你申请到 email.scope,可改用 /2/users/:id?user.fields=email
|
||||
String email = username + "@twitter.com";
|
||||
logger.debug("Processing user {} with email {}", username, email);
|
||||
return Optional.of(processUser(email, username, avatar, mode));
|
||||
return Optional.of(processUser(email, username, avatar, mode, viaInvite));
|
||||
}
|
||||
|
||||
private User processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode) {
|
||||
private AuthResult processUser(String email, String username, String avatar, com.openisle.model.RegisterMode mode, boolean viaInvite) {
|
||||
Optional<User> existing = userRepository.findByEmail(email);
|
||||
if (existing.isPresent()) {
|
||||
User user = existing.get();
|
||||
@@ -119,7 +120,7 @@ public class TwitterAuthService {
|
||||
userRepository.save(user);
|
||||
}
|
||||
logger.debug("Existing user {} authenticated", user.getUsername());
|
||||
return user;
|
||||
return new AuthResult(user, false);
|
||||
}
|
||||
String baseUsername = username != null ? username : email.split("@")[0];
|
||||
String finalUsername = baseUsername;
|
||||
@@ -133,13 +134,13 @@ public class TwitterAuthService {
|
||||
user.setPassword("");
|
||||
user.setRole(Role.USER);
|
||||
user.setVerified(true);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT || viaInvite);
|
||||
if (avatar != null) {
|
||||
user.setAvatar(avatar);
|
||||
} else {
|
||||
user.setAvatar("https://twitter.com/" + finalUsername + "/profile_image");
|
||||
}
|
||||
logger.debug("Creating new user {}", finalUsername);
|
||||
return userRepository.save(user);
|
||||
return new AuthResult(userRepository.save(user), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,13 @@ public class UserService {
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public User registerWithInvite(String username, String email, String password) {
|
||||
User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT);
|
||||
user.setVerified(true);
|
||||
user.setVerificationCode(null);
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
private String genCode() {
|
||||
return String.format("%06d", new Random().nextInt(1000000));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user