From cc3765c284057cf414200545e87537a69b8ffae2 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Fri, 2 Oct 2026 23:07:10 +0100 Subject: [PATCH] add repos and services (wip) --- .../com/ddf/vodsystem/dto/TokenPackage.java | 7 +++ .../repositories/RefreshTokenRepository.java | 10 +++ .../repositories/TokenFamilyRepository.java | 9 +++ .../ddf/vodsystem/security/TokenService.java | 63 +++++++++++++++++++ .../ddf/vodsystem/services/UserService.java | 28 +++++++-- src/main/resources/application.properties | 5 ++ 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/dto/TokenPackage.java create mode 100644 src/main/java/com/ddf/vodsystem/repositories/RefreshTokenRepository.java create mode 100644 src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java create mode 100644 src/main/java/com/ddf/vodsystem/security/TokenService.java diff --git a/src/main/java/com/ddf/vodsystem/dto/TokenPackage.java b/src/main/java/com/ddf/vodsystem/dto/TokenPackage.java new file mode 100644 index 0000000..315f466 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/dto/TokenPackage.java @@ -0,0 +1,7 @@ +package com.ddf.vodsystem.dto; + +public record TokenPackage ( + String refreshToken, + String accessToken +) { +} diff --git a/src/main/java/com/ddf/vodsystem/repositories/RefreshTokenRepository.java b/src/main/java/com/ddf/vodsystem/repositories/RefreshTokenRepository.java new file mode 100644 index 0000000..eead9cf --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/RefreshTokenRepository.java @@ -0,0 +1,10 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RefreshTokenRepository extends JpaRepository { + +} diff --git a/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java b/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java new file mode 100644 index 0000000..9fbbf90 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java @@ -0,0 +1,9 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.TokenFamily; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TokenFamilyRepository extends JpaRepository { +} diff --git a/src/main/java/com/ddf/vodsystem/security/TokenService.java b/src/main/java/com/ddf/vodsystem/security/TokenService.java new file mode 100644 index 0000000..0294e08 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/security/TokenService.java @@ -0,0 +1,63 @@ +package com.ddf.vodsystem.security; + +import com.ddf.vodsystem.entities.RefreshToken; +import com.ddf.vodsystem.entities.TokenFamily; +import com.ddf.vodsystem.entities.User; +import com.ddf.vodsystem.repositories.RefreshTokenRepository; +import com.ddf.vodsystem.repositories.TokenFamilyRepository; +import org.springframework.stereotype.Service; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.time.Instant; + +@Service +public class TokenService { + private final TokenFamilyRepository tokenFamilyRepository; + private final RefreshTokenRepository refreshTokenRepository; + + private static final SecureRandom RNG = new SecureRandom(); + + public TokenService ( + TokenFamilyRepository tokenFamilyRepository, + RefreshTokenRepository refreshTokenRepository + ) { + this.tokenFamilyRepository = tokenFamilyRepository; + this.refreshTokenRepository = refreshTokenRepository; + } + + public TokenFamily createTokenFamily(User user, long expiryTimeMs) { + Instant now = Instant.now(); + + TokenFamily tokenFamily = new TokenFamily(); + tokenFamily.setUser(user); + tokenFamily.setCreatedAt(now); + tokenFamily.setExpiresAt(now.plusMillis(expiryTimeMs)); + + return tokenFamilyRepository.saveAndFlush(tokenFamily); + } + + public RefreshToken getRefreshToken( + byte[] token, + TokenFamily tokenFamily, + long expiryTimeMs + ) { + Instant now = Instant.now(); + + RefreshToken refreshToken = new RefreshToken(); + refreshToken.setTokenFamily(tokenFamily); + refreshToken.setTokenHash(hashToken(token)); + refreshToken.setCreatedAt(now); + refreshToken.setExpiresAt(now.plusMillis(expiryTimeMs)); + return refreshTokenRepository.saveAndFlush(refreshToken); + } + + private static byte[] hashToken(byte[] token) { + try { + return MessageDigest.getInstance("SHA-256").digest(token); // 32 bytes + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/main/java/com/ddf/vodsystem/services/UserService.java b/src/main/java/com/ddf/vodsystem/services/UserService.java index f29d16c..acd40d1 100644 --- a/src/main/java/com/ddf/vodsystem/services/UserService.java +++ b/src/main/java/com/ddf/vodsystem/services/UserService.java @@ -1,9 +1,12 @@ package com.ddf.vodsystem.services; +import com.ddf.vodsystem.dto.TokenPackage; +import com.ddf.vodsystem.entities.TokenFamily; import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.exceptions.NotAuthenticated; import com.ddf.vodsystem.repositories.UserRepository; import com.ddf.vodsystem.security.JwtService; +import com.ddf.vodsystem.security.TokenService; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; @@ -20,6 +23,7 @@ import java.security.SecureRandom; import java.time.Instant; import java.util.Collections; import java.util.HexFormat; +import java.util.MissingResourceException; import java.util.Optional; @Service @@ -29,10 +33,12 @@ public class UserService { private final JwtService jwtService; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private final TokenService tokenService; public UserService(UserRepository userRepository, JwtService jwtService, - @Value("${google.client.id}") String googleClientId) { + @Value("${google.client.id}") String googleClientId, + TokenService tokenService) { this.userRepository = userRepository; NetHttpTransport transport = new NetHttpTransport(); @@ -42,6 +48,7 @@ public class UserService { .setAudience(Collections.singletonList(googleClientId)) .build(); this.jwtService = jwtService; + this.tokenService = tokenService; } /** @@ -85,9 +92,20 @@ public class UserService { User googleUser = getGoogleUser(googleIdToken); User user = createOrUpdateUser(googleUser); + TokenFamily tokenFamily = tokenService.createTokenFamily(user, ) + String jwt = jwtService.generateToken(user.getId()); + + return jwtService.generateToken(user.getId()); } + public TokenPackage refresh(String refreshToken) { + if (refreshToken == null) { + throw new NotAuthenticated("Missing refresh token"); + } + + } + /** * Looks up a user by their stream key. * @@ -104,7 +122,7 @@ public class UserService { if (existingUser.isEmpty()) { user.setRole(0); user.setCreatedAt(Instant.now()); - user.setStreamKey(generateStreamKey()); + user.setStreamKey(HexFormat.of().formatHex(generateRandomBytes(24))); return userRepository.saveAndFlush(user); } @@ -140,9 +158,9 @@ public class UserService { } } - private String generateStreamKey() { - byte[] bytes = new byte[24]; + private byte[] generateRandomBytes(int length) { + byte[] bytes = new byte[length]; SECURE_RANDOM.nextBytes(bytes); - return HexFormat.of().formatHex(bytes); + return bytes; } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3ff5b7a..1109b33 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,7 +21,12 @@ spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} spring.security.oauth2.client.registration.google.scope=profile,email jwt.secret.key=${JWT_SECRET_KEY} +# 1 Hour jwt.expiration=3600000 +# 7 days +refresh-token.expiration=604800000 +# 90 days +token-family.expiration=7776000000 # Frontend frontend.url=${FRONTEND_URL}