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

@@ -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);