14 standardize and clean api and fix bruno configuration (#25)

* ADD JWT authentication support with token generation and validation

* ADD JWT handling after successful login

* ADD user authentication and standardize user retrieval

* COMBINE token dtos

* ADD JWT authentication filter

* IMPROVE token handling

* STANDARDIZE API endpoints and improve JWT handling

* REMOVE extra logging

* REMOVE redundant job existence checks

* UPDATE Bruno Google token

* REFACTOR some classes

* ADD JWT cookie check

* ADD AuthProvider and CORS configuration; UPDATE API endpoints for consistency

* ADD JWT validation check;

* ADD profile picture to database

* ADD reload after login to update page

* PATCH login issue

* REMOVE unused classes

* ADJUST logging in JwtFilter

* REMOVE unused React component
This commit is contained in:
Dylan De Faoite
2025-08-10 22:41:37 +02:00
committed by GitHub
parent 20f7ec8db4
commit 662966f138
35 changed files with 916 additions and 252 deletions

View File

@@ -0,0 +1,28 @@
package com.ddf.vodsystem.configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Value("${frontend.url}")
private String frontendUrl;
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/v1/**")
.allowedOrigins(frontendUrl)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}

View File

@@ -1,51 +1,49 @@
package com.ddf.vodsystem.configuration;
import com.ddf.vodsystem.security.CustomOAuth2UserService;
import com.ddf.vodsystem.security.JwtFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
@Value("${frontend.url}")
private String frontendUrl;
public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
private final JwtFilter jwtFilter;
public SecurityConfig(JwtFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/download/clip/**").authenticated()
.requestMatchers(HttpMethod.OPTIONS, "/api/v1/**").permitAll()
.requestMatchers("/api/v1/download/clip/**","/api/v1/clips/", "/api/v1/clips/**").authenticated()
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/user").permitAll()
.requestMatchers("/api/v1/upload", "/api/v1/download/**").permitAll()
.requestMatchers("/api/v1/edit/**", "/api/v1/process/**", "/api/v1/progress/**", "/api/v1/convert/**").permitAll()
.requestMatchers("/api/v1/metadata/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(successHandler()))
.logout(logout -> logout
.logoutUrl("/api/v1/auth/logout")
.logoutSuccessHandler(logoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();

View File

@@ -1,45 +0,0 @@
package com.ddf.vodsystem.controllers;
import com.ddf.vodsystem.dto.APIResponse;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/auth/")
public class AuthController {
@GetMapping("/user")
public ResponseEntity<APIResponse<Map<String, Object>>> user(@AuthenticationPrincipal OAuth2User principal) {
if (principal == null) {
throw new NotAuthenticated("User is not authenticated");
}
if (
principal.getAttribute("email") == null
|| principal.getAttribute("name") == null
|| principal.getAttribute("picture") == null)
{
return ResponseEntity.status(HttpStatus.BAD_REQUEST).
body(new APIResponse<>(
"error",
"Required user attributes are missing",
null
));
}
return ResponseEntity.ok(
new APIResponse<>("success", "User details retrieved successfully", Map.of(
"name", principal.getAttribute("name"),
"email", principal.getAttribute("email"),
"profilePicture", principal.getAttribute("picture"))
)
);
}
}

View File

@@ -8,15 +8,11 @@ import com.ddf.vodsystem.services.ClipService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RestController
@RequestMapping("/api/v1/clips")
public class ClipController {
private final ClipService clipService;
@@ -25,12 +21,8 @@ public class ClipController {
this.clipService = clipService;
}
@GetMapping("/")
public ResponseEntity<APIResponse<List<ClipDTO>>> getClips(@AuthenticationPrincipal OAuth2User principal) {
if (principal == null) {
throw new NotAuthenticated("User is not authenticated");
}
@GetMapping("")
public ResponseEntity<APIResponse<List<ClipDTO>>> getClips() {
List<Clip> clips = clipService.getClipsByUser();
List<ClipDTO> clipDTOs = clips.stream()
.map(this::convertToDTO)
@@ -42,11 +34,7 @@ public class ClipController {
}
@GetMapping("/{id}")
public ResponseEntity<APIResponse<ClipDTO>> getClipById(@AuthenticationPrincipal OAuth2User principal, @PathVariable Long id) {
if (principal == null) {
throw new NotAuthenticated("User is not authenticated");
}
public ResponseEntity<APIResponse<ClipDTO>> getClipById(@PathVariable Long id) {
Clip clip = clipService.getClipById(id);
if (clip == null) {
return ResponseEntity.notFound().build();

View File

@@ -73,7 +73,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(NotAuthenticated.class)
public ResponseEntity<APIResponse<Void>> handleNotAuthenticated(NotAuthenticated ex) {
logger.error("NotAuthenticated: {}", ex.getMessage());
APIResponse<Void> response = new APIResponse<>(ERROR, "User is not authenticated", null);
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
}

View File

@@ -0,0 +1,54 @@
package com.ddf.vodsystem.controllers;
import com.ddf.vodsystem.dto.APIResponse;
import com.ddf.vodsystem.dto.TokenDTO;
import com.ddf.vodsystem.entities.User;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.services.UserService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/auth/")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/user")
public ResponseEntity<APIResponse<User>> user() {
User user = userService.getLoggedInUser();
if (user == null) {
throw new NotAuthenticated("User not authenticated");
}
return ResponseEntity.ok(
new APIResponse<>("success", "User retrieved successfully", user)
);
}
@PostMapping("/login")
public ResponseEntity<APIResponse<TokenDTO>> login(@RequestBody TokenDTO tokenDTO,
HttpServletResponse response) {
String jwt = userService.login(tokenDTO.getToken());
ResponseCookie cookie = ResponseCookie.from("token", jwt)
.httpOnly(true)
.maxAge(60 * 60 * 24)
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader("Set-Cookie", cookie.toString());
return ResponseEntity.ok(
new APIResponse<>("success", "Logged in successfully", new TokenDTO(jwt))
);
}
}

View File

@@ -1,9 +1,6 @@
package com.ddf.vodsystem.dto;
import java.io.File;
import org.springframework.security.core.context.SecurityContext;
import lombok.Data;
@Data
@@ -16,13 +13,13 @@ public class Job {
private VideoMetadata inputVideoMetadata;
private VideoMetadata outputVideoMetadata = new VideoMetadata();
// security
private SecurityContext securityContext;
// job status
private JobStatus status = new JobStatus();
public Job(String uuid, File inputFile, File outputFile, VideoMetadata inputVideoMetadata) {
public Job(String uuid,
File inputFile,
File outputFile,
VideoMetadata inputVideoMetadata) {
this.uuid = uuid;
this.inputFile = inputFile;
this.outputFile = outputFile;

View File

@@ -0,0 +1,10 @@
package com.ddf.vodsystem.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class TokenDTO {
private String token;
}

View File

@@ -26,6 +26,9 @@ public class User {
@Column(name = "name", nullable = false)
private String name;
@Column(name = "profile_picture_url")
private String profilePictureUrl;
@Column(name = "role", nullable = false)
private Integer role; // 0: user, 1: admin

View File

@@ -1,36 +0,0 @@
package com.ddf.vodsystem.security;
import com.ddf.vodsystem.entities.User;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection;
import java.util.Map;
@Getter
public class CustomOAuth2User implements OAuth2User {
private final OAuth2User oauth2User;
private final User user;
public CustomOAuth2User(OAuth2User oauth2User, User user) {
this.oauth2User = oauth2User;
this.user = user;
}
@Override
public Map<String, Object> getAttributes() {
return oauth2User.getAttributes();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oauth2User.getAuthorities();
}
@Override
public String getName() {
return oauth2User.getName();
}
}

View File

@@ -1,47 +0,0 @@
package com.ddf.vodsystem.security;
import com.ddf.vodsystem.entities.User;
import com.ddf.vodsystem.repositories.UserRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
public CustomOAuth2UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public CustomOAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
var delegate = new DefaultOAuth2UserService();
var oAuth2User = delegate.loadUser(userRequest);
String email = oAuth2User.getAttribute("email");
String name = oAuth2User.getAttribute("name");
String googleId = oAuth2User.getAttribute("sub");
User user = userRepository.findByGoogleId(googleId)
.orElseGet(() -> {
User newUser = new User();
newUser.setEmail(email);
newUser.setName(name);
newUser.setGoogleId(googleId);
newUser.setUsername(email);
newUser.setRole(0);
newUser.setCreatedAt(LocalDateTime.now());
return userRepository.save(newUser);
});
return new CustomOAuth2User(oAuth2User, user);
}
}

View File

@@ -0,0 +1,90 @@
package com.ddf.vodsystem.security;
import com.ddf.vodsystem.entities.User;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.services.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@Component
public class JwtFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserService userService;
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
public JwtFilter(JwtService jwtService,
UserService userService) {
this.jwtService = jwtService;
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String jwt = null;
// 1. Try to get the JWT from the Authorization header
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
logger.debug("JWT found in Authorization header");
jwt = authorizationHeader.substring(7);
}
// 2. If no JWT was found in the header, try to get it from a cookie
if (jwt == null) {
logger.debug("JWT not found in Authorization header, checking cookies");
Cookie[] cookies = request.getCookies();
if (cookies != null) {
jwt = Arrays.stream(cookies)
.filter(cookie -> "token".equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse(null);
}
}
if (jwt == null) {
logger.debug("No JWT found in request");
filterChain.doFilter(request, response);
return;
}
logger.debug("JWT found in request");
Long userId = jwtService.validateTokenAndGetUserId(jwt);
if (userId == null) {
logger.warn("Invalid JWT: {}", jwt);
filterChain.doFilter(request, response);
return;
}
User user;
try {
user = userService.getUserById(userId);
} catch (NotAuthenticated e) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = new UsernamePasswordAuthenticationToken(user, jwt, List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,54 @@
package com.ddf.vodsystem.security;
import java.util.Date;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.stereotype.Service;
@Service
public class JwtService {
private final long jwtExpiration;
private final Algorithm algorithm; // Algorithm is now initialized after secret key is available
private static final String USER_ID_CLAIM = "userId";
private static final String ISSUER = "vodsystem";
public JwtService(@Value("${jwt.secret.key}") String jwtSecretKey,
@Value("${jwt.expiration}") long jwtExpiration) {
this.jwtExpiration = jwtExpiration;
this.algorithm = Algorithm.HMAC256(jwtSecretKey);
}
public String generateToken(Long userId) {
return JWT.create()
.withClaim(USER_ID_CLAIM, userId)
.withIssuer(ISSUER)
.withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration))
.sign(algorithm);
}
public Long validateTokenAndGetUserId(String token) {
try {
JWTVerifier verifier = JWT.require(algorithm)
.withClaimPresence(USER_ID_CLAIM)
.withIssuer(ISSUER)
.build();
DecodedJWT jwt = verifier.verify(token);
if (jwt.getExpiresAt() == null || jwt.getExpiresAt().before(new Date())) {
return null;
}
return jwt.getClaim(USER_ID_CLAIM).asLong();
} catch (JwtException | IllegalArgumentException | TokenExpiredException ignored) {
return null;
}
}
}

View File

@@ -8,7 +8,9 @@ import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ExecutionException;
import com.ddf.vodsystem.exceptions.FFMPEGException;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.repositories.ClipRepository;
import com.ddf.vodsystem.services.media.CompressionService;
@@ -64,7 +66,7 @@ public class ClipService {
ProgressTracker progress)
throws IOException, InterruptedException {
User user = userService.getUser();
User user = userService.getLoggedInUser();
metadataService.normalizeVideoMetadata(inputMetadata, outputMetadata);
compressionService.compress(inputFile, outputFile, outputMetadata, progress)
.thenRun(() -> {
@@ -75,7 +77,7 @@ public class ClipService {
}
public List<Clip> getClipsByUser() {
User user = userService.getUser();
User user = userService.getLoggedInUser();
if (user == null) {
logger.warn("No authenticated user found");
@@ -124,7 +126,7 @@ public class ClipService {
}
public boolean isAuthenticatedForClip(Clip clip) {
User user = userService.getUser();
User user = userService.getLoggedInUser();
if (user == null || clip == null) {
return false;
}
@@ -132,14 +134,22 @@ public class ClipService {
}
private void persistClip(VideoMetadata videoMetadata,
User user,
File tempFile,
String fileName) {
User user,
File tempFile,
String fileName) {
// Move clip from temp to output directory
File clipFile = directoryService.getUserClipsFile(user.getId(), fileName);
File thumbnailFile = directoryService.getUserThumbnailsFile(user.getId(), fileName + ".png");
directoryService.cutFile(tempFile, clipFile);
VideoMetadata clipMetadata;
try {
clipMetadata = metadataService.getVideoMetadata(clipFile).get();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new FFMPEGException("Error retrieving video metadata for clip: " + e.getMessage());
}
try {
thumbnailService.createThumbnail(clipFile, thumbnailFile, 0.0f);
@@ -152,15 +162,17 @@ public class ClipService {
Clip clip = new Clip();
clip.setUser(user);
clip.setTitle(videoMetadata.getTitle() != null ? videoMetadata.getTitle() : "Untitled Clip");
clip.setDescription(videoMetadata.getDescription());
clip.setDescription(videoMetadata.getDescription() != null ? videoMetadata.getDescription() : "");
clip.setCreatedAt(LocalDateTime.now());
clip.setWidth(videoMetadata.getWidth());
clip.setHeight(videoMetadata.getHeight());
clip.setFps(videoMetadata.getFps());
clip.setDuration(videoMetadata.getEndPoint() - videoMetadata.getStartPoint());
clip.setFileSize(videoMetadata.getFileSize());
clip.setWidth(clipMetadata.getWidth());
clip.setHeight(clipMetadata.getHeight());
clip.setFps(clipMetadata.getFps());
clip.setDuration(clipMetadata.getEndPoint() - clipMetadata.getStartPoint());
clip.setFileSize(clipMetadata.getFileSize());
clip.setVideoPath(clipFile.getPath());
clip.setThumbnailPath(thumbnailFile.getPath());
clipRepository.save(clip);
logger.info("Clip created successfully with ID: {}", clip.getId());
}
}

View File

@@ -30,10 +30,6 @@ public class DownloadService {
public Resource downloadInput(String uuid) {
Job job = jobService.getJob(uuid);
if (job == null) {
throw new JobNotFound("Job doesn't exist");
}
File file = job.getInputFile();
return new FileSystemResource(file);
}
@@ -41,10 +37,6 @@ public class DownloadService {
public Resource downloadOutput(String uuid) {
Job job = jobService.getJob(uuid);
if (job == null) {
throw new JobNotFound("Job doesn't exist");
}
if (!job.getStatus().getProcess().isComplete()) {
throw new JobNotFinished("Job is not finished");
}

View File

@@ -46,13 +46,7 @@ public class UploadService {
// add job
logger.info("Uploaded file and creating job with UUID: {}", uuid);
VideoMetadata videoMetadata;
try {
videoMetadata = metadataService.getVideoMetadata(inputFile).get(5, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException | InterruptedException e) {
Thread.currentThread().interrupt();
throw new FFMPEGException(e.getMessage());
}
VideoMetadata videoMetadata = getMetadataWithTimeout(inputFile);
Job job = new Job(uuid, inputFile, outputFile, videoMetadata);
jobService.add(job);
@@ -67,4 +61,13 @@ public class UploadService {
return Base64.getUrlEncoder().withoutPadding().encodeToString(bb.array());
}
private VideoMetadata getMetadataWithTimeout(File file) {
try {
return metadataService.getVideoMetadata(file).get(5, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException | InterruptedException e) {
Thread.currentThread().interrupt();
throw new FFMPEGException(e.getMessage());
}
}
}

View File

@@ -1,18 +1,111 @@
package com.ddf.vodsystem.services;
import com.ddf.vodsystem.entities.User;
import com.ddf.vodsystem.security.CustomOAuth2User;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.repositories.UserRepository;
import com.ddf.vodsystem.security.JwtService;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Optional;
@Service
public class UserService {
public User getUser() {
private final GoogleIdTokenVerifier verifier;
private final UserRepository userRepository;
private final JwtService jwtService;
public UserService(UserRepository userRepository,
JwtService jwtService,
@Value("${google.client.id}") String googleClientId) {
this.userRepository = userRepository;
NetHttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new GsonFactory();
this.verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(googleClientId))
.build();
this.jwtService = jwtService;
}
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new NotAuthenticated("User not found"));
}
public User getLoggedInUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomOAuth2User oAuth2user) {
return oAuth2user.getUser();
if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof User) {
return (User) auth.getPrincipal();
}
return null;
}
public String login(String idToken) {
GoogleIdToken googleIdToken = getGoogleIdToken(idToken);
String googleId = googleIdToken.getPayload().getSubject();
if (googleId == null) {
throw new NotAuthenticated("Invalid ID token");
}
User googleUser = getGoogleUser(googleIdToken);
User user = createOrUpdateUser(googleUser);
return jwtService.generateToken(user.getId());
}
private User createOrUpdateUser(User user) {
Optional<User> existingUser = userRepository.findByGoogleId(user.getGoogleId());
if (existingUser.isEmpty()) {
user.setRole(0);
user.setCreatedAt(LocalDateTime.now());
return userRepository.saveAndFlush(user);
}
User existing = existingUser.get();
existing.setEmail(user.getEmail());
existing.setName(user.getName());
existing.setProfilePictureUrl(user.getProfilePictureUrl());
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());
}
}
}

View File

@@ -61,6 +61,7 @@ public class MetadataService {
}
}
private VideoMetadata parseVideoMetadata(JsonNode node) {
VideoMetadata metadata = new VideoMetadata();
metadata.setStartPoint(0f);