rajah 11 месяцев назад
Родитель
Сommit
303d16c3bb
35 измененных файлов с 438 добавлено и 69 удалено
  1. 12 2
      bin/main/application.properties
  2. 21 0
      src/main/java/fr/triplea/demovote/dao/RefreshTokenRepository.java
  3. 21 0
      src/main/java/fr/triplea/demovote/dto/JsonErrorResponse.java
  4. 19 0
      src/main/java/fr/triplea/demovote/dto/RefreshTokenTransfer.java
  5. 8 18
      src/main/java/fr/triplea/demovote/dto/UserCredentials.java
  6. 4 0
      src/main/java/fr/triplea/demovote/model/Participant.java
  7. 54 0
      src/main/java/fr/triplea/demovote/model/RefreshToken.java
  8. 51 0
      src/main/java/fr/triplea/demovote/security/GlobalExceptionHandler.java
  9. 10 0
      src/main/java/fr/triplea/demovote/security/ResourceNotFoundException.java
  10. 6 4
      src/main/java/fr/triplea/demovote/security/SecurityConfig.java
  11. 53 0
      src/main/java/fr/triplea/demovote/security/ServerConfig.java
  12. 1 1
      src/main/java/fr/triplea/demovote/security/jwt/JwtAuthenticationEntryPoint.java
  13. 2 1
      src/main/java/fr/triplea/demovote/security/jwt/JwtTokenFilter.java
  14. 23 13
      src/main/java/fr/triplea/demovote/security/jwt/JwtTokenUtil.java
  15. 14 0
      src/main/java/fr/triplea/demovote/security/jwt/RefreshTokenException.java
  16. 48 0
      src/main/java/fr/triplea/demovote/security/jwt/RefreshTokenService.java
  17. 3 3
      src/main/java/fr/triplea/demovote/security/xss/XssFilter.java
  18. 3 3
      src/main/java/fr/triplea/demovote/security/xss/XssRequestWrapper.java
  19. 1 1
      src/main/java/fr/triplea/demovote/security/xss/XssSanitizerUtil.java
  20. 1 1
      src/main/java/fr/triplea/demovote/web/controller/AccountController.java
  21. 53 8
      src/main/java/fr/triplea/demovote/web/controller/AuthController.java
  22. 1 1
      src/main/java/fr/triplea/demovote/web/controller/BulletinController.java
  23. 1 1
      src/main/java/fr/triplea/demovote/web/controller/CategorieController.java
  24. 1 1
      src/main/java/fr/triplea/demovote/web/controller/DiversController.java
  25. 1 1
      src/main/java/fr/triplea/demovote/web/controller/MessageController.java
  26. 3 3
      src/main/java/fr/triplea/demovote/web/controller/ParticipantController.java
  27. 1 1
      src/main/java/fr/triplea/demovote/web/controller/PreferenceController.java
  28. 1 1
      src/main/java/fr/triplea/demovote/web/controller/PresentationController.java
  29. 1 1
      src/main/java/fr/triplea/demovote/web/controller/ProductionController.java
  30. 1 1
      src/main/java/fr/triplea/demovote/web/controller/VariableController.java
  31. 12 2
      src/main/resources/application.properties
  32. 3 0
      src/main/resources/langs/messages_en.properties
  33. 4 1
      src/main/resources/langs/messages_fr.properties
  34. BIN
      src/main/resources/myCertificate.crt
  35. BIN
      src/main/resources/springboot.p12

+ 12 - 2
bin/main/application.properties

@@ -12,9 +12,16 @@ spring.messages.basename=messages,langs.messages
 
 logging.level.org.springframework=INFO
 
+server.port=8443
+server.ssl.enabled=true
+server.ssl.key-store=classpath:springboot.p12
+server.ssl.key-store-password=password
+server.ssl.key-store-type=pkcs12
+server.ssl.key-alias=springboot
+server.ssl.key-password: password
 server.servlet.context-path=/demovote-api/v1
 
-#logging.file.name=logs/demovote.log
+ #logging.file.name=logs/demovote.log
 
 #logging.logback.rollingpolicy.file-name-pattern=logs/%d{yyyy-MM, aux}/demovote.%d{yyyy-MM-dd}.%i.log
 #logging.logback.rollingpolicy.max-file-size=2MB
@@ -24,5 +31,8 @@ server.servlet.context-path=/demovote-api/v1
 admin.email.address=xxx.xxx@free.fr
 
 jwttoken.secret=ee3c5233e2fde173cf7f401e5fb45aa47937a76f45e5fdcff29bedba6e6ea61c695ac0058ead08561261445b6f547aced2e335c2cc210fab42bc4b5317f987e9297b5c0e19eb21f38d0fd5cf69ba4cfa7ed0fa02d299a34ed6fdf22b508997a573075c4c375e6f3e45c7cb82c78958b2f3d47a87145eb74334023429401f584928a224796093afad62696dc9bab1cfdf4368a2263a13480b80faf873ca1f1cb067da4db75ec53379e0da1d3a61572dbeebfc3484f6f2ed333c96154036d0c22a5a2a59895ee6711e77e604e8b8c5b0a45fb2cce05298d12c25e1f9a6ba4d030ce2e480c1e3ad3fe0551c2a136bd18635c829f7eb4f92f4e34ec67e95bb966dac
-jwttoken.expiration=3600000 
+jwttoken.expiration=900000 
+jwttoken.jwtRefreshExpirationMs= 86400000
+#jwttoken.expiration=60000 
+#jwttoken.jwtRefreshExpirationMs= 120000
 

+ 21 - 0
src/main/java/fr/triplea/demovote/dao/RefreshTokenRepository.java

@@ -0,0 +1,21 @@
+package fr.triplea.demovote.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.NativeQuery;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import fr.triplea.demovote.model.RefreshToken;
+
+@Repository
+public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Integer> 
+{
+  
+  RefreshToken findByToken(String token);
+
+  @Modifying
+  @NativeQuery("DELETE FROM vote.refreshtoken AS r WHERE r.numero_participant = :numero ")
+  int deleteByNumeroParticipant(@Param("numero") int numeroParticipant);
+  
+}

+ 21 - 0
src/main/java/fr/triplea/demovote/dto/JsonErrorResponse.java

@@ -0,0 +1,21 @@
+package fr.triplea.demovote.dto;
+
+public class JsonErrorResponse 
+{
+
+  private int status;
+  private String message;
+
+  public JsonErrorResponse(int status, String message) 
+  {
+    this.status = status;
+    this.message = message;
+  }
+
+  public void setStatus(int i) { this.status = i; }
+  public int getStatus() { return this.status; }
+  
+  public void setMessage(String s) { this.message = new String(s); }
+  public String getMessage() { return this.message; }
+  
+}

+ 19 - 0
src/main/java/fr/triplea/demovote/dto/RefreshTokenTransfer.java

@@ -0,0 +1,19 @@
+package fr.triplea.demovote.dto;
+
+import jakarta.validation.constraints.NotBlank;
+
+public class RefreshTokenTransfer 
+{
+
+  private String accessToken;
+
+  @NotBlank
+  private String refreshToken;
+
+  public void setAccessToken(String s) { this.accessToken = new String(s); }
+  public String getAccessToken() { return this.accessToken; }
+
+  public void setRefreshToken(String s) { this.refreshToken = new String(s); }
+  public String getRefreshToken() { return this.refreshToken; }
+  
+}

+ 8 - 18
src/main/java/fr/triplea/demovote/dto/UserCredentials.java

@@ -1,6 +1,5 @@
 package fr.triplea.demovote.dto;
 
-import org.json.JSONObject;
 
 public class UserCredentials
 {
@@ -26,29 +25,20 @@ public class UserCredentials
   public String getRole() { return this.role; }
   public boolean hasRole() { if (this.role != null) { if (!(this.role.isBlank())) { return true; }} return false; }
   
-  private String token;
-  public void setToken(String s) { this.token = new String(s); }
-  public String getToken() { return this.token; }
+  private String accessToken;
+  public void setAccessToken(String s) { this.accessToken = new String(s); }
+  public String getAccessToken() { return this.accessToken; }
   
+  private String refreshToken;
+  public void setRefreshToken(String s) { this.refreshToken = new String(s); }
+  public String getRefreshToken() { return this.refreshToken; }
+
   private String erreur;
   public void setErreur(String s) { this.erreur = new String(s); }
   public String getErreur() { return this.erreur; }
 
   public UserCredentials() {}
-  
-  public String toJSONString()
-  {
-    JSONObject jo = new JSONObject();
-    
-    jo.put("username", this.username);
-    jo.put("password", this.password);
-    jo.put("nom", this.nom);
-    jo.put("prenom", this.prenom);
-    jo.put("role", hasRole() ? this.role : null);
-
-    return jo.toString();
-  }
-  
+   
   @Override
   public String toString() 
   {

+ 4 - 0
src/main/java/fr/triplea/demovote/model/Participant.java

@@ -29,6 +29,7 @@ import jakarta.persistence.JoinColumn;
 import jakarta.persistence.JoinTable;
 import jakarta.persistence.ManyToMany;
 import jakarta.persistence.OneToMany;
+import jakarta.persistence.OneToOne;
 import jakarta.persistence.Table;
 import jakarta.persistence.Temporal;
 import jakarta.persistence.TemporalType;
@@ -140,6 +141,9 @@ public class Participant
   @Column(name = "flag_arrive")
   private Boolean arrived = false;
   
+  @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "participant")
+  private RefreshToken refreshToken;
+  
   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "participant")
   private List<Preference> preferences;
   

+ 54 - 0
src/main/java/fr/triplea/demovote/model/RefreshToken.java

@@ -0,0 +1,54 @@
+package fr.triplea.demovote.model;
+
+
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
+
+import java.time.Instant;
+
+import org.springframework.util.StringUtils;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+
+@Entity(name = "vote.refreshtoken")
+@Table(name = "refreshtoken")
+public class RefreshToken 
+{
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id", nullable = false)
+  private Integer id;
+
+  @OneToOne
+  @JoinColumn(name = "numero_participant", referencedColumnName = "numero_participant")
+  private Participant participant;
+
+  @Column(nullable = false, unique = true, length = 4000)
+  private String token;
+
+  @Temporal(TemporalType.TIMESTAMP)
+  @Column(nullable = false)
+  private Instant expiryDate;
+
+  
+  public void setId(Integer i) { this.id = i; }
+  public Integer getId() { return this.id; }
+  
+  public void setParticipant(Participant p) { this.participant = p; }
+  public Participant getParticipant() { return this.participant; }
+  
+  public void setToken(String str) { if (str != null) { this.token = StringUtils.truncate(str, 4000); } }
+  public String getToken() { return this.token; }
+ 
+  public void setExpiryDate(Instant d) { this.expiryDate = d; }
+  public Instant getExpiryDate() { return this.expiryDate; }
+
+}

+ 51 - 0
src/main/java/fr/triplea/demovote/security/GlobalExceptionHandler.java

@@ -0,0 +1,51 @@
+package fr.triplea.demovote.security;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import fr.triplea.demovote.dto.JsonErrorResponse;
+import fr.triplea.demovote.security.jwt.RefreshTokenException;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler 
+{
+
+  @ExceptionHandler(value = RefreshTokenException.class)
+  @ResponseStatus(HttpStatus.FORBIDDEN)
+  public ResponseEntity<JsonErrorResponse> handleTokenRefreshException(RefreshTokenException ex) 
+  {
+    JsonErrorResponse jer = new JsonErrorResponse(HttpStatus.FORBIDDEN.value(), ex.getMessage());
+
+    return new ResponseEntity<>(jer, HttpStatus.FORBIDDEN);
+  }
+
+  @ExceptionHandler(Exception.class)
+  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+  public ResponseEntity<JsonErrorResponse> handleAllExceptions(Exception ex) 
+  {
+    JsonErrorResponse jer = new JsonErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage());
+
+    return new ResponseEntity<>(jer, HttpStatus.INTERNAL_SERVER_ERROR);
+  }
+
+  @ExceptionHandler(NullPointerException.class)
+  @ResponseStatus(HttpStatus.BAD_REQUEST)
+  public ResponseEntity<JsonErrorResponse> handleNullPointerException(NullPointerException ex) 
+  {
+    JsonErrorResponse jer = new JsonErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
+
+    return new ResponseEntity<>(jer, HttpStatus.BAD_REQUEST);
+  }
+
+  @ExceptionHandler(ResourceNotFoundException.class)
+  public ResponseEntity<JsonErrorResponse> handleNotFound(ResourceNotFoundException ex) 
+  {
+    JsonErrorResponse jer = new JsonErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
+    
+    return new ResponseEntity<>(jer, HttpStatus.NOT_FOUND);
+  }
+
+}

+ 10 - 0
src/main/java/fr/triplea/demovote/security/ResourceNotFoundException.java

@@ -0,0 +1,10 @@
+package fr.triplea.demovote.security;
+
+public class ResourceNotFoundException extends RuntimeException 
+{
+
+  private static final long serialVersionUID = 7551018488670969405L;
+
+  public ResourceNotFoundException(String message) { super(message); }
+  
+}

+ 6 - 4
src/main/java/fr/triplea/demovote/security/SecurityConfig.java

@@ -19,6 +19,9 @@ import org.springframework.security.web.context.SecurityContextRepository;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import fr.triplea.demovote.security.jwt.JwtTokenFilter;
+
 import org.springframework.security.web.context.DelegatingSecurityContextRepository;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
@@ -30,7 +33,7 @@ public class SecurityConfig
 {
  
   // TODO: CSRF-TOKEN
-  // TODO: gérer le 403 au niveau du frontend (en cas d'expiration du JWT -> refreshToken)
+  // TODO: HTTPS
   // TODO: déconnexion automatique après timeout
 
   @Bean
@@ -78,13 +81,12 @@ public class SecurityConfig
   
   Class<? extends UsernamePasswordAuthenticationFilter> clazz = UsernamePasswordAuthenticationFilter.class;
 
-  @Bean
-  public XSSFilter xssFilter() { return new XSSFilter(); }
-   
+  
   @Bean
   SecurityFilterChain securityFilterChain(HttpSecurity http, SecurityContextRepository securityContextRepository) throws Exception 
   {
     http.csrf(csrf -> csrf.disable())
+        .requiresChannel(channel -> channel.anyRequest().requiresSecure())
         .authenticationProvider(authenticationProvider())
         .authorizeHttpRequests((ahreq) -> ahreq
           .requestMatchers("/divers/**", "/sign/**").permitAll()

+ 53 - 0
src/main/java/fr/triplea/demovote/security/ServerConfig.java

@@ -0,0 +1,53 @@
+package fr.triplea.demovote.security;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ServerConfig 
+{
+
+  @Bean
+  public ServletWebServerFactory servletContainer() 
+  {
+    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
+      
+      @Override
+      protected void postProcessContext(Context context) {
+        var securityConstraint = new SecurityConstraint();
+        
+        securityConstraint.setUserConstraint("CONFIDENTIAL");
+        
+        var collection = new SecurityCollection();
+        
+        collection.addPattern("/*");
+        
+        securityConstraint.addCollection(collection);
+        
+        context.addConstraint(securityConstraint);
+      }
+    };
+    
+    tomcat.addAdditionalTomcatConnectors(getHttpConnector());
+    return tomcat;
+  }
+
+  private Connector getHttpConnector() 
+  {
+    var connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
+  
+    connector.setScheme("http");
+    connector.setPort(8080);
+    connector.setSecure(false);
+    connector.setRedirectPort(8443);
+    
+    return connector;
+  }
+
+}

+ 1 - 1
src/main/java/fr/triplea/demovote/security/JwtAuthenticationEntryPoint.java → src/main/java/fr/triplea/demovote/security/jwt/JwtAuthenticationEntryPoint.java

@@ -1,4 +1,4 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.jwt;
 
 import java.io.IOException;
 import org.springframework.security.core.AuthenticationException;

+ 2 - 1
src/main/java/fr/triplea/demovote/security/JwtTokenFilter.java → src/main/java/fr/triplea/demovote/security/jwt/JwtTokenFilter.java

@@ -1,4 +1,4 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.jwt;
 
 import java.io.IOException;
 
@@ -9,6 +9,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import fr.triplea.demovote.model.MyUserDetails;
+import fr.triplea.demovote.security.MyUserDetailsService;
 
 import org.springframework.util.StringUtils;
 

+ 23 - 13
src/main/java/fr/triplea/demovote/security/JwtTokenUtil.java → src/main/java/fr/triplea/demovote/security/jwt/JwtTokenUtil.java

@@ -1,10 +1,12 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.jwt;
 
 import java.io.UnsupportedEncodingException;
 import java.util.Date;
 
 import javax.crypto.spec.SecretKeySpec;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.core.Authentication;
 import org.springframework.stereotype.Component;
@@ -19,6 +21,9 @@ import io.jsonwebtoken.UnsupportedJwtException;
 @Component
 public class JwtTokenUtil 
 {
+  //@SuppressWarnings("unused") 
+  private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtil.class);
+
   @Value("${jwttoken.secret}")
   private String jwtTokenSecret;
  
@@ -31,16 +36,21 @@ public class JwtTokenUtil
   
   public String generateJwtToken(Authentication authentication) 
   {
-    setSecretKey();
-    
     MyUserDetails userPrincipal = (MyUserDetails)authentication.getPrincipal();
-        
+    
+    return generateTokenFromPseudonyme(userPrincipal.getUsername());
+  }
+  
+  public String generateTokenFromPseudonyme(String pseudonyme) 
+  {
+    setSecretKey();
+
     return Jwts.builder()
-           .subject(userPrincipal.getUsername())
-           .issuedAt(new Date(System.currentTimeMillis()))
-           .expiration(new Date(System.currentTimeMillis() + jwtTokenExpiration))
-           .signWith(secret_key)
-           .compact();
+        .subject(pseudonyme)
+        .issuedAt(new Date(System.currentTimeMillis()))
+        .expiration(new Date(System.currentTimeMillis() + jwtTokenExpiration))
+        .signWith(secret_key)
+        .compact();
   }
   
   public boolean validateJwtToken(String token) 
@@ -53,10 +63,10 @@ public class JwtTokenUtil
      
       return true;
     }
-    catch(UnsupportedJwtException exp) { System.out.println("claimsJws argument does not represent Claims JWS" + exp.getMessage()); }
-    catch(MalformedJwtException exp) { System.out.println("claimsJws string is not a valid JWS" + exp.getMessage()); }
-    catch(ExpiredJwtException exp) { System.out.println("Claims has an expiration time before the method is invoked" + exp.getMessage()); }
-    catch(IllegalArgumentException exp) { System.out.println("claimsJws string is null or empty or only whitespace" + exp.getMessage()); }
+    catch(UnsupportedJwtException exp) { LOG.error("claimsJws argument does not represent Claims JWS" + exp.getMessage()); }
+    catch(MalformedJwtException exp) { LOG.error("claimsJws string is not a valid JWS" + exp.getMessage()); }
+    catch(ExpiredJwtException exp) { LOG.error("Claims has an expiration time before the method is invoked" + exp.getMessage()); }
+    catch(IllegalArgumentException exp) { LOG.error("claimsJws string is null or empty or only whitespace" + exp.getMessage()); }
     
     return false;
   }

+ 14 - 0
src/main/java/fr/triplea/demovote/security/jwt/RefreshTokenException.java

@@ -0,0 +1,14 @@
+package fr.triplea.demovote.security.jwt;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.FORBIDDEN)
+public class RefreshTokenException extends RuntimeException 
+{
+
+  private static final long serialVersionUID = -3612406590151878124L;
+
+  public RefreshTokenException(String token, String message) { super(String.format("Failed for [%s]: %s", token, message)); }
+  
+}

+ 48 - 0
src/main/java/fr/triplea/demovote/security/jwt/RefreshTokenService.java

@@ -0,0 +1,48 @@
+package fr.triplea.demovote.security.jwt;
+
+import java.time.Instant;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import fr.triplea.demovote.dao.ParticipantRepository;
+import fr.triplea.demovote.dao.RefreshTokenRepository;
+import fr.triplea.demovote.model.RefreshToken;
+import jakarta.transaction.Transactional;
+
+@Service
+public class RefreshTokenService 
+{
+
+  @Value("${jwttoken.jwtRefreshExpirationMs}")
+  private Long refreshTokenDurationMs;
+
+  @Autowired
+  private RefreshTokenRepository refreshTokenRepository;
+
+  @Autowired
+  private ParticipantRepository participantRepository;
+
+  public RefreshToken findByToken(String token) { return refreshTokenRepository.findByToken(token); }
+
+  public RefreshToken createRefreshToken(Integer userId) 
+  {
+    RefreshToken refreshToken = new RefreshToken();
+
+    refreshToken.setParticipant(participantRepository.findById(userId).get());
+    refreshToken.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
+    refreshToken.setToken(UUID.randomUUID().toString());
+
+    refreshToken = refreshTokenRepository.save(refreshToken);
+    
+    return refreshToken;
+  }
+
+  public RefreshToken verifyExpiration(RefreshToken token) { if (token.getExpiryDate().compareTo(Instant.now()) < 0) { refreshTokenRepository.delete(token); return null; } return token; }
+
+  @Transactional
+  public int deleteByNumeroParticipant(Integer numeroParticipant) { return refreshTokenRepository.deleteByNumeroParticipant(numeroParticipant); }
+  
+}

+ 3 - 3
src/main/java/fr/triplea/demovote/security/XSSFilter.java → src/main/java/fr/triplea/demovote/security/xss/XssFilter.java

@@ -1,4 +1,4 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.xss;
 
 import java.io.IOException;
 
@@ -14,13 +14,13 @@ import jakarta.servlet.http.HttpServletRequest;
 
 @WebFilter("/*")
 @Order(1)
-public class XSSFilter implements Filter 
+public class XssFilter implements Filter 
 {
   
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
   {
-    chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
+    chain.doFilter(new XssRequestWrapper((HttpServletRequest) request), response);
   }
 
 }

+ 3 - 3
src/main/java/fr/triplea/demovote/security/XSSRequestWrapper.java → src/main/java/fr/triplea/demovote/security/xss/XssRequestWrapper.java

@@ -1,4 +1,4 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.xss;
 
 import jakarta.servlet.ReadListener;
 import jakarta.servlet.ServletInputStream;
@@ -16,12 +16,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
-public class XSSRequestWrapper extends HttpServletRequestWrapper 
+public class XssRequestWrapper extends HttpServletRequestWrapper 
 {
   //@SuppressWarnings("unused") 
   //private static final Logger LOG = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
 
-  public XSSRequestWrapper(HttpServletRequest request) { super(request); }
+  public XssRequestWrapper(HttpServletRequest request) { super(request); }
 
   @Override
   public String[] getParameterValues(String parameter) 

+ 1 - 1
src/main/java/fr/triplea/demovote/security/XssSanitizerUtil.java → src/main/java/fr/triplea/demovote/security/xss/XssSanitizerUtil.java

@@ -1,4 +1,4 @@
-package fr.triplea.demovote.security;
+package fr.triplea.demovote.security.xss;
 
 import java.util.ArrayList;
 import java.util.List;

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/AccountController.java

@@ -18,7 +18,7 @@ import fr.triplea.demovote.dto.ParticipantTransfer;
 import fr.triplea.demovote.model.Participant;
 
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/account")
 public class AccountController 

+ 53 - 8
src/main/java/fr/triplea/demovote/web/controller/AuthController.java

@@ -21,16 +21,22 @@ import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.LocaleResolver;
 
 import fr.triplea.demovote.dao.ParticipantRepository;
+import fr.triplea.demovote.dto.ParticipantTransfer;
+import fr.triplea.demovote.dto.RefreshTokenTransfer;
 import fr.triplea.demovote.dto.UserCredentials;
 import fr.triplea.demovote.model.Participant;
+import fr.triplea.demovote.model.RefreshToken;
 import fr.triplea.demovote.model.Role;
-import fr.triplea.demovote.security.JwtTokenUtil;
 import fr.triplea.demovote.security.MyUserDetailsService;
+import fr.triplea.demovote.security.jwt.JwtTokenUtil;
+import fr.triplea.demovote.security.jwt.RefreshTokenException;
+import fr.triplea.demovote.security.jwt.RefreshTokenService;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
 
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/sign")
 public class AuthController 
@@ -47,6 +53,9 @@ public class AuthController
   @Autowired
   private JwtTokenUtil jwtTokenUtil;
 
+  @Autowired
+  RefreshTokenService refreshTokenService;
+
   @Autowired
   private ParticipantRepository participantRepository;
 
@@ -81,15 +90,18 @@ public class AuthController
         
         String token = jwtTokenUtil.generateJwtToken(authentication);
         
-        // TODO: add jwtoken in user credentials for frontend
+        refreshTokenService.deleteByNumeroParticipant(found.getNumeroParticipant());
         
+        RefreshToken refreshToken = refreshTokenService.createRefreshToken(found.getNumeroParticipant());
+                
         uc = new UserCredentials();
         
         uc.setUsername(usrn);
         uc.setPassword("<success@auth>");
         uc.setNom(found.getNom());
         uc.setPrenom(found.getPrenom());
-        uc.setToken(token);
+        uc.setAccessToken(token);
+        uc.setRefreshToken(refreshToken.getToken());
         uc.setErreur("");
 
         List<Role> roles = found.getRoles();
@@ -108,7 +120,8 @@ public class AuthController
         uc.setPassword("");
         uc.setNom("");
         uc.setPrenom("");
-        uc.setToken("");
+        uc.setAccessToken("");
+        uc.setRefreshToken("");
         uc.setRole("");
         uc.setErreur(messageSource.getMessage("auth.password.mismatches", null, locale));
        
@@ -122,24 +135,56 @@ public class AuthController
     uc.setPassword("");
     uc.setNom("");
     uc.setPrenom("");
-    uc.setToken("");
+    uc.setAccessToken("");
+    uc.setRefreshToken("");
     uc.setRole("");
     uc.setErreur(messageSource.getMessage("auth.user.notfound", null, locale));
    
     return ResponseEntity.ok(uc);
   }
 
+  @PostMapping("/refresh")
+  public ResponseEntity<?> refreshtoken(@Valid @RequestBody RefreshTokenTransfer rtt, HttpServletRequest request) 
+  {
+    Locale locale = localeResolver.resolveLocale(request);
+
+    String refreshTokenActif = rtt.getRefreshToken();
+
+    RefreshToken found = refreshTokenService.findByToken(refreshTokenActif);
+    
+    if (found == null) { throw new RefreshTokenException(refreshTokenActif, messageSource.getMessage("refreshtoken.notfound", null, locale)); }
+
+    found = refreshTokenService.verifyExpiration(found);
+    
+    if (found == null) { throw new RefreshTokenException(refreshTokenActif, messageSource.getMessage("refreshtoken.expired", null, locale)); }
+
+    Participant participant = found.getParticipant();
+        
+    rtt.setAccessToken(jwtTokenUtil.generateTokenFromPseudonyme(participant.getPseudonyme()));
+    
+    return ResponseEntity.ok(rtt);
+  }
+  
   @PostMapping("/out")
-  public ResponseEntity<UserCredentials> signOut()
+  public ResponseEntity<UserCredentials> signOut(final Authentication authentication)
   {
+    if (authentication != null)
+    {
+      ParticipantTransfer found = participantRepository.searchByPseudonyme(authentication.getName());
+      
+      if (found != null) { refreshTokenService.deleteByNumeroParticipant(found.numeroParticipant()); }
+    }
+
     SecurityContextHolder.clearContext();
-    
+        
     UserCredentials uc = new UserCredentials();
     
     uc.setUsername("");
     uc.setPassword("");
     uc.setNom("");
     uc.setPrenom("");
+    uc.setAccessToken("");
+    uc.setRefreshToken("");
     uc.setRole("");
 
     return ResponseEntity.ok(uc);

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/BulletinController.java

@@ -24,7 +24,7 @@ import fr.triplea.demovote.model.Categorie;
 import fr.triplea.demovote.model.Participant;
 import fr.triplea.demovote.model.Production;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/urne")
 public class BulletinController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/CategorieController.java

@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
 import fr.triplea.demovote.dao.CategorieRepository;
 import fr.triplea.demovote.model.Categorie;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/categorie")
 public class CategorieController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/DiversController.java

@@ -12,7 +12,7 @@ import fr.triplea.demovote.dao.VariableRepository;
 import fr.triplea.demovote.dto.JourneesTransfer;
 import fr.triplea.demovote.dto.MessagesTransfer;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/divers")
 public class DiversController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/MessageController.java

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
 import fr.triplea.demovote.dao.MessageRepository;
 import fr.triplea.demovote.model.Message;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/message")
 public class MessageController 

+ 3 - 3
src/main/java/fr/triplea/demovote/web/controller/ParticipantController.java

@@ -31,7 +31,7 @@ import fr.triplea.demovote.model.ParticipantModePaiement;
 import fr.triplea.demovote.model.ParticipantStatut;
 
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/participant")
 public class ParticipantController 
@@ -47,11 +47,11 @@ public class ParticipantController
 
   @GetMapping(value = "/list")
   @PreAuthorize("hasRole('ORGA')")
-  public List<ParticipantList> getList(@RequestParam("nom") String filtreNom, @RequestParam("statut") int filtreStatut, @RequestParam("arrive") int filtreArrive, @RequestParam("tri") int tri) 
+  public List<ParticipantList> getList(@RequestParam("nom") String filtreNom, @RequestParam("statut") int filtreStatut, @RequestParam("arrive") int filtreArrive, @RequestParam("tri") int choixTri) 
   { 
     if (filtreNom != null) { if (filtreNom.isBlank()) { filtreNom = null; } else { filtreNom = filtreNom.trim().toUpperCase(); } }
     
-    if (tri == 1) { return participantRepository.getListOrderedByDateInscription(filtreNom, filtreStatut, filtreArrive); }
+    if (choixTri == 1) { return participantRepository.getListOrderedByDateInscription(filtreNom, filtreStatut, filtreArrive); }
     
     return participantRepository.getListOrderedByNom(filtreNom, filtreStatut, filtreArrive);
   }

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/PreferenceController.java

@@ -18,7 +18,7 @@ import fr.triplea.demovote.dao.PreferenceRepository;
 import fr.triplea.demovote.model.Participant;
 import fr.triplea.demovote.model.Preference;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/preference")
 public class PreferenceController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/PresentationController.java

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
 import fr.triplea.demovote.dao.PresentationRepository;
 import fr.triplea.demovote.model.Presentation;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/presentation")
 public class PresentationController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/ProductionController.java

@@ -36,7 +36,7 @@ import fr.triplea.demovote.model.ProductionType;
 import io.hypersistence.utils.hibernate.type.basic.Inet;
 import jakarta.servlet.http.HttpServletRequest;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/production")
 public class ProductionController 

+ 1 - 1
src/main/java/fr/triplea/demovote/web/controller/VariableController.java

@@ -23,7 +23,7 @@ import fr.triplea.demovote.dao.VariableRepository;
 import fr.triplea.demovote.dto.VariableTypeOptionList;
 import fr.triplea.demovote.model.Variable;
 
-@CrossOrigin(origins = "http://localhost:4200")
+@CrossOrigin(origins = "https://localhost:4200")
 @RestController
 @RequestMapping("/variable")
 public class VariableController 

+ 12 - 2
src/main/resources/application.properties

@@ -12,9 +12,16 @@ spring.messages.basename=messages,langs.messages
 
 logging.level.org.springframework=INFO
 
+server.port=8443
+server.ssl.enabled=true
+server.ssl.key-store=classpath:springboot.p12
+server.ssl.key-store-password=password
+server.ssl.key-store-type=pkcs12
+server.ssl.key-alias=springboot
+server.ssl.key-password: password
 server.servlet.context-path=/demovote-api/v1
 
-#logging.file.name=logs/demovote.log
+ #logging.file.name=logs/demovote.log
 
 #logging.logback.rollingpolicy.file-name-pattern=logs/%d{yyyy-MM, aux}/demovote.%d{yyyy-MM-dd}.%i.log
 #logging.logback.rollingpolicy.max-file-size=2MB
@@ -24,5 +31,8 @@ server.servlet.context-path=/demovote-api/v1
 admin.email.address=xxx.xxx@free.fr
 
 jwttoken.secret=ee3c5233e2fde173cf7f401e5fb45aa47937a76f45e5fdcff29bedba6e6ea61c695ac0058ead08561261445b6f547aced2e335c2cc210fab42bc4b5317f987e9297b5c0e19eb21f38d0fd5cf69ba4cfa7ed0fa02d299a34ed6fdf22b508997a573075c4c375e6f3e45c7cb82c78958b2f3d47a87145eb74334023429401f584928a224796093afad62696dc9bab1cfdf4368a2263a13480b80faf873ca1f1cb067da4db75ec53379e0da1d3a61572dbeebfc3484f6f2ed333c96154036d0c22a5a2a59895ee6711e77e604e8b8c5b0a45fb2cce05298d12c25e1f9a6ba4d030ce2e480c1e3ad3fe0551c2a136bd18635c829f7eb4f92f4e34ec67e95bb966dac
-jwttoken.expiration=3600000 
+jwttoken.expiration=900000 
+jwttoken.jwtRefreshExpirationMs= 86400000
+#jwttoken.expiration=60000 
+#jwttoken.jwtRefreshExpirationMs= 120000
 

+ 3 - 0
src/main/resources/langs/messages_en.properties

@@ -1,2 +1,5 @@
 auth.password.mismatches=The password does not match any entrant's password.
 auth.user.notfound=The nickname does not seem to be used by any entrant.
+
+refreshtoken.notfound=the currrent RefreshToken is not present in the database.
+refreshtoken.expired=the current RefreshToken has expired. Please re-sign in.

+ 4 - 1
src/main/resources/langs/messages_fr.properties

@@ -1,2 +1,5 @@
 auth.password.mismatches=Le mot de passe ne correspond pas à ce participant.
-auth.user.notfound=Le pseudonyme ne semble pas être celui d'un participant.
+auth.user.notfound=Le pseudonyme ne semble pas être celui d'un participant.
+
+refreshtoken.notfound=ce RefreshToken introuvable dans la base de données.
+refreshtoken.expired=ce RefreshToken a expiré. Merci de vous reconnecter.

BIN
src/main/resources/myCertificate.crt


BIN
src/main/resources/springboot.p12