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:
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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"))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
10
src/main/java/com/ddf/vodsystem/dto/TokenDTO.java
Normal file
10
src/main/java/com/ddf/vodsystem/dto/TokenDTO.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.ddf.vodsystem.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class TokenDTO {
|
||||
private String token;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
90
src/main/java/com/ddf/vodsystem/security/JwtFilter.java
Normal file
90
src/main/java/com/ddf/vodsystem/security/JwtFilter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
54
src/main/java/com/ddf/vodsystem/security/JwtService.java
Normal file
54
src/main/java/com/ddf/vodsystem/security/JwtService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public class MetadataService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private VideoMetadata parseVideoMetadata(JsonNode node) {
|
||||
VideoMetadata metadata = new VideoMetadata();
|
||||
metadata.setStartPoint(0f);
|
||||
|
||||
Reference in New Issue
Block a user