Wallah l'auth elle est censée taffer, mais pt'être y'a un douille, check ça mon reuf 👀

This commit is contained in:
Guams 2024-12-20 15:45:17 +01:00
parent d14ae93f95
commit c3dd337c7d
12 changed files with 124 additions and 67 deletions

View File

@ -3,7 +3,7 @@ package com.guams.review.configuration;
import com.guams.review.exception.AlreadyExistsException; import com.guams.review.exception.AlreadyExistsException;
import com.guams.review.exception.InvalidNameOrPasswordException; import com.guams.review.exception.InvalidNameOrPasswordException;
import com.guams.review.exception.NotFoundException; import com.guams.review.exception.NotFoundException;
import com.guams.review.exception.UnauthorizedExecption; import com.guams.review.exception.ForbiddenExecption;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
@ -24,11 +24,11 @@ public class Advice {
@ExceptionHandler(value = InvalidNameOrPasswordException.class) @ExceptionHandler(value = InvalidNameOrPasswordException.class)
public ResponseEntity<Object> handleInvalidNameOrPassword(InvalidNameOrPasswordException exception) { public ResponseEntity<Object> handleInvalidNameOrPassword(InvalidNameOrPasswordException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED); return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
} }
@ExceptionHandler(value = UnauthorizedExecption.class) @ExceptionHandler(value = ForbiddenExecption.class)
public ResponseEntity<Object> handleUnauthorizedExecption(UnauthorizedExecption exception) { public ResponseEntity<Object> handleUnauthorizedExecption(ForbiddenExecption exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED); return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
} }
} }

View File

@ -1,5 +1,4 @@
package com.guams.review.configuration; package com.guams.review.configuration;
import com.guams.review.model.dao.Author;
import com.guams.review.service.AuthorService; import com.guams.review.service.AuthorService;
import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;

View File

@ -3,6 +3,7 @@ import com.guams.review.service.AuthorService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -12,6 +13,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration @Configuration
@ -25,15 +31,36 @@ public class SpringSecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
return http return http
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // Ajout de la configuration CORS
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/api/authors/login", "/api/authors/register").permitAll() .requestMatchers(HttpMethod.GET,
.requestMatchers("/api/authors/me").authenticated() // Autorise les utilisateurs authentifiés "/api/authors",
.anyRequest().authenticated() "/api/authors/{id}",
"/api/authors/{id}/posts",
"/api/posts",
"/api/posts/{id}").permitAll() // Autorise les GET sur ces routes
.requestMatchers("/api/authors/login", "/api/authors/register").permitAll() // Autorise sans authentification
.requestMatchers("/api/authors/me").authenticated() // Requiert authentification
.anyRequest().authenticated() // Toutes les autres routes nécessitent une authentification
) )
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Ajoute le filtre JWT .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Ajoute le filtre JWT
.build(); .build();
} }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:4200")); // N'autorise que localhost:4200
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); // Spécifie les méthodes autorisées
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); // Limite les en-têtes autorisés
configuration.setAllowCredentials(true); // Autorise l'utilisation des cookies ou des tokens
configuration.setMaxAge(3600L); // Cache la configuration CORS pendant 1 heure
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // Applique les règles à toutes les routes
return source;
}
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();

View File

@ -1,16 +0,0 @@
package com.guams.review.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**"); // autorise toutes les requêtes externe
}
}

View File

@ -4,13 +4,14 @@ import com.guams.review.configuration.JwtTokenUtil;
import com.guams.review.exception.AlreadyExistsException; import com.guams.review.exception.AlreadyExistsException;
import com.guams.review.exception.InvalidNameOrPasswordException; import com.guams.review.exception.InvalidNameOrPasswordException;
import com.guams.review.exception.NotFoundException; import com.guams.review.exception.NotFoundException;
import com.guams.review.exception.UnauthorizedExecption; import com.guams.review.exception.ForbiddenExecption;
import com.guams.review.model.AuthorRepository; import com.guams.review.model.AuthorRepository;
import com.guams.review.model.dao.Author; import com.guams.review.model.dao.Author;
import com.guams.review.model.dao.AuthorToken;
import com.guams.review.model.dao.Post; import com.guams.review.model.dao.Post;
import com.guams.review.service.AuthorService; import com.guams.review.service.AuthorService;
import com.guams.review.service.mapper.Mapper; import com.guams.review.service.mapper.Mapper;
import com.guams.review.service.mapper.AuthorWithToken; import com.guams.review.service.mapper.ReturnableAuthor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -41,12 +42,12 @@ public class AuthorController {
private final Mapper mapper; private final Mapper mapper;
@GetMapping @GetMapping
public List<AuthorWithToken> getUsers() { public List<ReturnableAuthor> getUsers() {
return authorService.list(); return authorService.list();
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public AuthorWithToken findUser(@PathVariable UUID id) { public ReturnableAuthor findUser(@PathVariable UUID id) {
Author author = authorService.findById(id).orElseThrow(() -> new NotFoundException("Author not found")); Author author = authorService.findById(id).orElseThrow(() -> new NotFoundException("Author not found"));
return mapper.mapAuthor(author); return mapper.mapAuthor(author);
} }
@ -82,14 +83,14 @@ public class AuthorController {
} }
@PostMapping("/login") @PostMapping("/login")
public AuthorWithToken authorLogin(@RequestBody Author author) { public AuthorToken authorLogin(@RequestBody Author author) {
try { try {
authenticationManager.authenticate( authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(author.getName(), author.getPassword()) new UsernamePasswordAuthenticationToken(author.getName(), author.getPassword())
); );
String token = jwtTokenUtil.generateToken(author.getName()); String token = jwtTokenUtil.generateToken(author.getName());
return mapper.mapAuthor(author).setToken(token); return new AuthorToken().setToken(token);
} catch (Exception e) { } catch (Exception e) {
throw new InvalidNameOrPasswordException(e.getMessage()); throw new InvalidNameOrPasswordException(e.getMessage());
} }
@ -105,10 +106,10 @@ public class AuthorController {
return new ResponseEntity<>(authorRepository.save(author.setRole("USER")).setPassword(""), HttpStatus.CREATED); return new ResponseEntity<>(authorRepository.save(author.setRole("USER")).setPassword(""), HttpStatus.CREATED);
} }
@GetMapping("/me") @GetMapping(value = "/me", produces = "application/json")
public Author getAuthenticatedUser(Authentication authentication) { public Author getAuthenticatedUser(Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
throw new UnauthorizedExecption("You are not authorized to access this resource"); throw new ForbiddenExecption("You are not authorized to access this resource");
} }
String username = authentication.getName(); String username = authentication.getName();
@ -120,5 +121,4 @@ public class AuthorController {
return author.setPassword(""); return author.setPassword("");
} }
} }

View File

@ -1,12 +1,17 @@
package com.guams.review.controller; package com.guams.review.controller;
import com.guams.review.exception.NotFoundException; import com.guams.review.exception.NotFoundException;
import com.guams.review.exception.ForbiddenExecption;
import com.guams.review.model.AuthorRepository;
import com.guams.review.model.dao.Author;
import com.guams.review.model.dao.Post; import com.guams.review.model.dao.Post;
import com.guams.review.service.AuthorService;
import com.guams.review.service.PostService; import com.guams.review.service.PostService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -21,6 +26,8 @@ import java.util.List;
public class PostController { public class PostController {
private final PostService postService; private final PostService postService;
private final AuthorService authorService;
private final AuthorRepository authorRepository;
@GetMapping @GetMapping
public List<Post> listPosts() { public List<Post> listPosts() {
@ -33,30 +40,59 @@ public class PostController {
} }
@PutMapping("/{id}") @PutMapping("/{id}")
public void updatePost(@PathVariable Long id, @RequestBody Post updatedPost) { public void updatePost(@PathVariable Long id, @RequestBody Post updatedPost, Authentication authentication) {
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); if (authentication == null) {
postService.insert(updatedPost throw new ForbiddenExecption("You have to login to do that");
.setId(postToUpdate.getId()) }
.setIllustration(postToUpdate.getIllustration()) Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
.setPublicationDate(postToUpdate.getPublicationDate())); //Si l'user authent possède ce post
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
postService.insert(updatedPost
.setId(postToUpdate.getId())
.setIllustration(postToUpdate.getIllustration())
.setPublicationDate(postToUpdate.getPublicationDate()));
} else {
throw new ForbiddenExecption("You do not have permission to update this post");
}
} }
@PutMapping(value = "{id}/illustration", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) @PutMapping(value = "{id}/illustration", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public void updateIllustration(@PathVariable Long id, @RequestPart MultipartFile illustration) throws IOException { public void updateIllustration(@PathVariable Long id, @RequestPart MultipartFile illustration, Authentication authentication) throws IOException {
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); if (authentication == null) {
postService.insert(postToUpdate.setIllustration(illustration.getBytes())); throw new ForbiddenExecption("You have to login to do that");
}
Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
Post postToUpdate = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
postService.insert(postToUpdate.setIllustration(illustration.getBytes()));
} else {
throw new ForbiddenExecption("You do not have permission to update this post");
}
} }
@PostMapping @PostMapping
public ResponseEntity<Post> addPost(@RequestBody Post postToCreate) { public ResponseEntity<Post> addPost(@RequestBody Post postToCreate, Authentication authentication) {
Assert.isNull(postToCreate.getId(), "Post id must be null"); Assert.isNull(postToCreate.getId(), "Post id must be null");
if (authentication == null) {
throw new ForbiddenExecption("You have to login to do that");
}
return new ResponseEntity<>(postService.insert(postToCreate.setPublicationDate(LocalDate.now())), HttpStatus.CREATED); return new ResponseEntity<>(postService.insert(postToCreate.setPublicationDate(LocalDate.now())), HttpStatus.CREATED);
} }
@DeleteMapping("{id}") @DeleteMapping("{id}")
public void deletePost(@PathVariable Long id) { public void deletePost(@PathVariable Long id, Authentication authentication) {
Post postToDelete = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found")); if (authentication == null) {
postService.delete(postToDelete.getId()); throw new ForbiddenExecption("You have to login to do that");
}
Author authenticatedAuthor = authorRepository.findByName(authentication.getName());
if (authorService.listPublicationOfAuthor(authenticatedAuthor.getId()).stream().map(Post::getId).toList().contains(id)) {
Post postToDelete = postService.findById(id).orElseThrow(() -> new NotFoundException("Post not found"));
postService.delete(postToDelete.getId());
} else {
throw new ForbiddenExecption("You do not have permission to delete this post");
}
} }
} }

View File

@ -0,0 +1,7 @@
package com.guams.review.exception;
public class ForbiddenExecption extends RuntimeException {
public ForbiddenExecption(String message) {
super(message);
}
}

View File

@ -1,7 +0,0 @@
package com.guams.review.exception;
public class UnauthorizedExecption extends RuntimeException {
public UnauthorizedExecption(String message) {
super(message);
}
}

View File

@ -0,0 +1,12 @@
package com.guams.review.model.dao;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@Getter
@Setter
@Accessors(chain = true)
public class AuthorToken {
String token;
}

View File

@ -1,13 +1,13 @@
package com.guams.review.service; package com.guams.review.service;
import com.guams.review.exception.NotFoundException; import com.guams.review.exception.NotFoundException;
import com.guams.review.exception.UnauthorizedExecption; import com.guams.review.exception.ForbiddenExecption;
import com.guams.review.model.AuthorRepository; import com.guams.review.model.AuthorRepository;
import com.guams.review.model.PostRepository; import com.guams.review.model.PostRepository;
import com.guams.review.model.dao.Author; import com.guams.review.model.dao.Author;
import com.guams.review.model.dao.Post; import com.guams.review.model.dao.Post;
import com.guams.review.service.mapper.Mapper; import com.guams.review.service.mapper.Mapper;
import com.guams.review.service.mapper.AuthorWithToken; import com.guams.review.service.mapper.ReturnableAuthor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -28,7 +28,7 @@ public class AuthorService implements UserDetailsService
private final AuthorRepository authorRepository; private final AuthorRepository authorRepository;
private final PostRepository postRepository; private final PostRepository postRepository;
public List<AuthorWithToken> list() { public List<ReturnableAuthor> list() {
return authorRepository.findAll().stream() return authorRepository.findAll().stream()
.map(mapper::mapAuthor) .map(mapper::mapAuthor)
.toList(); .toList();
@ -42,7 +42,7 @@ public class AuthorService implements UserDetailsService
return authorRepository.findById(id); return authorRepository.findById(id);
} }
public AuthorWithToken insert(Author author) { public ReturnableAuthor insert(Author author) {
return mapper.mapAuthor(authorRepository.save(author)); return mapper.mapAuthor(authorRepository.save(author));
} }
@ -58,15 +58,15 @@ public class AuthorService implements UserDetailsService
public Author verifyIfUserIsAuthorized(Authentication authentication, UUID id) { public Author verifyIfUserIsAuthorized(Authentication authentication, UUID id) {
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
throw new UnauthorizedExecption("You have to login first"); throw new ForbiddenExecption("You have to login first");
} }
Author authorToUpdate = findById(id).orElseThrow(() -> new NotFoundException("Author not found")); Author author = findById(id).orElseThrow(() -> new NotFoundException("Author not found"));
String username = authentication.getName(); String username = authentication.getName();
Author authorAuthenticated = authorRepository.findByName(username); Author authorAuthenticated = authorRepository.findByName(username);
if (authorAuthenticated.getId() != authorToUpdate.getId() && !authorAuthenticated.getRole().equals("ADMIN")) { if (authorAuthenticated.getId().compareTo(author.getId()) != 0 && !authorAuthenticated.getRole().equals("ADMIN")) {
throw new UnauthorizedExecption("Specified Author is not authorized to do that"); throw new ForbiddenExecption("Specified Author is not authorized to do that");
} }
return authorToUpdate; return author;
} }
public void delete(Author author) { public void delete(Author author) {

View File

@ -8,8 +8,8 @@ public class Mapper {
public Mapper() {} public Mapper() {}
public AuthorWithToken mapAuthor(Author author) { public ReturnableAuthor mapAuthor(Author author) {
return new AuthorWithToken() return new ReturnableAuthor()
.setId(author.getId()) .setId(author.getId())
.setName(author.getName()) .setName(author.getName())
.setRole(author.getRole()) .setRole(author.getRole())

View File

@ -9,10 +9,9 @@ import java.util.UUID;
@Getter @Getter
@Setter @Setter
@Accessors(chain = true) @Accessors(chain = true)
public class AuthorWithToken { public class ReturnableAuthor {
private UUID id; private UUID id;
private String name; private String name;
private byte[] profilePicture; private byte[] profilePicture;
private String role; private String role;
private String token;
} }