extract Google logic into separate adapter
All checks were successful
CI / Maven unit tests (push) Successful in 13s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

This commit is contained in:
2026-10-04 14:54:23 +01:00
parent 05361409b2
commit cbb8c2195f
5 changed files with 81 additions and 50 deletions

View File

@@ -0,0 +1,9 @@
package com.ddf.vodsystem.dto;
public record GoogleUser(
String googleId,
String email,
String name,
String profilePictureUrl
) {
}

View File

@@ -15,9 +15,17 @@ public record AuthProperties (
@NotBlank @Size(min = 32) String jwtSecret, @NotBlank @Size(min = 32) String jwtSecret,
@Valid @NotNull Expiration expiration @Valid @NotNull Expiration expiration
) { ) {
/**
*
* @param jwt Duration object as to the length of the access token
* @param refreshToken Duration of refresh token
* @param tokenFamily Duration of token family
*/
public record Expiration ( public record Expiration (
@NotNull Duration jwt, @NotNull Duration jwt,
@NotNull Duration refreshToken, @NotNull Duration refreshToken,
@NotNull Duration tokenFamily @NotNull Duration tokenFamily,
@NotNull Duration gracePeriod
) {} ) {}
} }

View File

@@ -0,0 +1,41 @@
package com.ddf.vodsystem.security;
import com.ddf.vodsystem.dto.GoogleUser;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.security.GeneralSecurityException;
@Service
public class GoogleVerifierService {
private final GoogleIdTokenVerifier verifier;
public GoogleVerifierService(GoogleIdTokenVerifier verifier) {
this.verifier = verifier;
}
public GoogleUser verify(String idToken) {
GoogleIdToken token;
try {
token = verifier.verify(idToken);
} catch (GeneralSecurityException | IOException e) {
throw new NotAuthenticated("Invalid ID token: " + e.getMessage());
}
String googleId = token.getPayload().getSubject();
if (googleId == null) {
throw new NotAuthenticated("Google ID does not exist");
}
return new GoogleUser(
googleId,
token.getPayload().getEmail(),
(String) token.getPayload().get("name"),
(String) token.getPayload().get("picture")
);
}
}

View File

@@ -1,5 +1,6 @@
package com.ddf.vodsystem.services; package com.ddf.vodsystem.services;
import com.ddf.vodsystem.dto.GoogleUser;
import com.ddf.vodsystem.dto.TokenPackage; import com.ddf.vodsystem.dto.TokenPackage;
import com.ddf.vodsystem.dto.properties.AuthProperties; import com.ddf.vodsystem.dto.properties.AuthProperties;
import com.ddf.vodsystem.entities.RefreshToken; import com.ddf.vodsystem.entities.RefreshToken;
@@ -7,17 +8,14 @@ import com.ddf.vodsystem.entities.TokenFamily;
import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.entities.User;
import com.ddf.vodsystem.exceptions.NotAuthenticated; import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.repositories.UserRepository; import com.ddf.vodsystem.repositories.UserRepository;
import com.ddf.vodsystem.security.GoogleVerifierService;
import com.ddf.vodsystem.security.JwtService; import com.ddf.vodsystem.security.JwtService;
import com.ddf.vodsystem.security.TokenService; import com.ddf.vodsystem.security.TokenService;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -26,7 +24,7 @@ import java.util.Optional;
@Service @Service
public class UserService { public class UserService {
private final GoogleIdTokenVerifier verifier; private final GoogleVerifierService googleVerifierService;
private final UserRepository userRepository; private final UserRepository userRepository;
private final JwtService jwtService; private final JwtService jwtService;
@@ -39,11 +37,11 @@ public class UserService {
public UserService(UserRepository userRepository, public UserService(UserRepository userRepository,
JwtService jwtService, JwtService jwtService,
GoogleIdTokenVerifier verifier, GoogleVerifierService googleVerifierService,
AuthProperties props, AuthProperties props,
TokenService tokenService) { TokenService tokenService) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.verifier = verifier; this.googleVerifierService = googleVerifierService;
this.jwtService = jwtService; this.jwtService = jwtService;
this.tokenService = tokenService; this.tokenService = tokenService;
@@ -83,15 +81,8 @@ public class UserService {
*/ */
@Transactional @Transactional
public TokenPackage login(String idToken) { public TokenPackage login(String idToken) {
GoogleIdToken googleIdToken = getGoogleIdToken(idToken); GoogleUser googleUser = googleVerifierService.verify(idToken);
String googleId = googleIdToken.getPayload().getSubject(); User user = createOrUpdateGoogleUser(googleUser);
if (googleId == null) {
throw new NotAuthenticated("Invalid ID token");
}
User googleUser = getGoogleUser(googleIdToken);
User user = createOrUpdateUser(googleUser);
byte[] rawRefresh = generateRandomBytes(32); byte[] rawRefresh = generateRandomBytes(32);
TokenFamily tokenFamily = tokenService.createTokenFamily(user, tokenFamilyExpirationMs); TokenFamily tokenFamily = tokenService.createTokenFamily(user, tokenFamilyExpirationMs);
@@ -161,46 +152,25 @@ public class UserService {
return userRepository.findByStreamKey(streamKey); return userRepository.findByStreamKey(streamKey);
} }
private User createOrUpdateUser(User user) { private User createOrUpdateGoogleUser(GoogleUser googleUser) {
Optional<User> existingUser = userRepository.findByGoogleId(user.getGoogleId()); Optional<User> existingUser = userRepository.findByGoogleId(googleUser.googleId());
User user;
if (existingUser.isEmpty()) { if (existingUser.isEmpty()) {
user = new User();
user.setGoogleId(googleUser.googleId());
user.setUsername(googleUser.email());
user.setRole(0); user.setRole(0);
user.setCreatedAt(Instant.now()); user.setCreatedAt(Instant.now());
user.setStreamKey(HexFormat.of().formatHex(generateRandomBytes(24))); user.setStreamKey(HexFormat.of().formatHex(generateRandomBytes(24)));
return userRepository.saveAndFlush(user); } else {
user = existingUser.get();
} }
User existing = existingUser.get(); user.setEmail(googleUser.email());
existing.setEmail(user.getEmail()); user.setName(googleUser.name());
existing.setName(user.getName()); user.setProfilePictureUrl(googleUser.profilePictureUrl());
existing.setProfilePictureUrl(user.getProfilePictureUrl()); return userRepository.saveAndFlush(user);
existing.setUsername(user.getUsername());
return userRepository.saveAndFlush(existing);
}
private User getGoogleUser(GoogleIdToken idToken) {
String googleId = idToken.getPayload().getSubject();
String email = idToken.getPayload().getEmail();
String name = (String) idToken.getPayload().get("name");
String profilePictureUrl = (String) idToken.getPayload().get("picture");
User user = new User();
user.setGoogleId(googleId);
user.setEmail(email);
user.setName(name);
user.setUsername(email);
user.setProfilePictureUrl(profilePictureUrl);
return user;
}
private GoogleIdToken getGoogleIdToken(String idToken) {
try {
return verifier.verify(idToken);
} catch (GeneralSecurityException | IOException e) {
throw new NotAuthenticated("Invalid ID token: " + e.getMessage());
}
} }
private byte[] generateRandomBytes(int length) { private byte[] generateRandomBytes(int length) {

View File

@@ -28,6 +28,9 @@ app.security.expiration.refresh-token=604800000
# 90 days # 90 days
app.security.expiration.token-family=7776000000 app.security.expiration.token-family=7776000000
# 5 seconds
app.security.expiration.grace-period=5000
# Frontend # Frontend
frontend.url=${FRONTEND_URL} frontend.url=${FRONTEND_URL}