From 81e443b0b6567556d16a92cbbcda7c880122c01f Mon Sep 17 00:00:00 2001 From: Aurince AKAKPO Date: Thu, 2 Jul 2026 16:11:42 +0100 Subject: [PATCH] gestion creation user --- .../controllers/user/ProfileController.java | 2 +- .../controllers/user/UserController.java | 34 +++++++ .../io/gmss/fiscad/entities/user/User.java | 10 +++ .../implementations/user/UserServiceImpl.java | 90 ++++++++++++++++++- .../fiscad/interfaces/user/UserService.java | 3 + .../request/ResetPasswordByMailRequest.java | 12 +++ .../request/record/EMailBodyRequest.java | 19 ++++ .../request/record/EmailAttachmentInput.java | 11 +++ .../response/record/MailSendResponse.java | 17 ++++ .../repositories/user/UserRepository.java | 1 + .../gmss/fiscad/service/CallAPIService.java | 76 ++-------------- .../io/gmss/fiscad/service/MailService.java | 61 ++++++++++++- .../resources/application-test.properties | 13 ++- src/main/resources/application.properties | 6 +- 14 files changed, 274 insertions(+), 81 deletions(-) create mode 100755 src/main/java/io/gmss/fiscad/paylaods/request/ResetPasswordByMailRequest.java create mode 100644 src/main/java/io/gmss/fiscad/paylaods/request/record/EMailBodyRequest.java create mode 100644 src/main/java/io/gmss/fiscad/paylaods/request/record/EmailAttachmentInput.java create mode 100644 src/main/java/io/gmss/fiscad/paylaods/response/record/MailSendResponse.java diff --git a/src/main/java/io/gmss/fiscad/controllers/user/ProfileController.java b/src/main/java/io/gmss/fiscad/controllers/user/ProfileController.java index 3b6dc50..cce6791 100755 --- a/src/main/java/io/gmss/fiscad/controllers/user/ProfileController.java +++ b/src/main/java/io/gmss/fiscad/controllers/user/ProfileController.java @@ -23,7 +23,7 @@ import org.springframework.web.client.HttpClientErrorException; @RestController -@RequestMapping(value = "api/profil", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(value = "api/profile", produces = MediaType.APPLICATION_JSON_VALUE) @SecurityRequirement(name = "bearer") @Tag(name = "Profile") @CrossOrigin(origins = "*") diff --git a/src/main/java/io/gmss/fiscad/controllers/user/UserController.java b/src/main/java/io/gmss/fiscad/controllers/user/UserController.java index 643ed72..db670dc 100755 --- a/src/main/java/io/gmss/fiscad/controllers/user/UserController.java +++ b/src/main/java/io/gmss/fiscad/controllers/user/UserController.java @@ -8,6 +8,7 @@ import io.gmss.fiscad.exceptions.*; import io.gmss.fiscad.interfaces.user.UserService; import io.gmss.fiscad.paylaods.ApiResponse; import io.gmss.fiscad.paylaods.Login; +import io.gmss.fiscad.paylaods.request.ResetPasswordByMailRequest; import io.gmss.fiscad.paylaods.request.crudweb.UserPaylaodWeb; import io.gmss.fiscad.security.CurrentUser; import io.gmss.fiscad.security.UserPrincipal; @@ -119,6 +120,39 @@ public class UserController { } } + + @PostMapping("/reset-password-by-mail") + public ResponseEntity resetUserPasswordByMail(@RequestBody @Valid @Validated ResetPasswordByMailRequest resetPasswordByMailRequest) { + try { + if(userService.validationTokenResetPassword(resetPasswordByMailRequest.getToken())){ + UserPaylaodWeb userPaylaodWeb = userService.resetPasswordByMail(resetPasswordByMailRequest.getToken(), resetPasswordByMailRequest.getNewPassword()); + return new ResponseEntity<>( + new ApiResponse<>(true, userPaylaodWeb.getLogin(), "Mot de passe réinitialiser avec succès."), + HttpStatus.OK + ); + }else { + return new ResponseEntity<>( + new ApiResponse<>(false, "Token invalide ou expiré."), + HttpStatus.OK + ); + } + + } catch (HttpClientErrorException.MethodNotAllowed e) { + logger.error(e.getLocalizedMessage()); + return new ResponseEntity<>(new ApiResponse(false, null, "Method POST/GET is required."), HttpStatus.OK); + } catch (NotFoundException | BadRequestException | MyFileNotFoundException | ResourceNotFoundException | + FileStorageException e) { + logger.error(e.getLocalizedMessage()); + return new ResponseEntity<>(new ApiResponse(false, null, e.getMessage()), HttpStatus.OK); + } catch (NullPointerException e) { + logger.error(e.getLocalizedMessage()); + return new ResponseEntity<>(new ApiResponse(false, null, "Null value has been detected {" + e.getMessage() + "}."), HttpStatus.OK); + } catch (Exception e) { + logger.error(e.getLocalizedMessage()); + return new ResponseEntity<>(new ApiResponse(false, null, "An error has been occur and the content is {" + e.getMessage() + "}."), HttpStatus.OK); + } + } + @PostMapping("/validate-user-account/{userName}") @PreAuthorize("hasAuthority('UPDATE_USER')") public ResponseEntity validateUserAccount(@PathVariable String userName) { diff --git a/src/main/java/io/gmss/fiscad/entities/user/User.java b/src/main/java/io/gmss/fiscad/entities/user/User.java index 9a77b33..72520c6 100755 --- a/src/main/java/io/gmss/fiscad/entities/user/User.java +++ b/src/main/java/io/gmss/fiscad/entities/user/User.java @@ -1,6 +1,9 @@ package io.gmss.fiscad.entities.user; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.gmss.fiscad.deserializer.LocalDateDeserializer; import io.gmss.fiscad.entities.BaseEntity; import io.gmss.fiscad.entities.decoupage.Secteur; import io.gmss.fiscad.entities.infocad.metier.Enquete; @@ -16,6 +19,8 @@ import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -54,6 +59,11 @@ public class User extends BaseEntity implements Serializable { @Column(columnDefinition = "boolean default false") private boolean resetPassword; + private String token; + + private LocalDateTime tokenDate; + + @OneToMany(mappedBy = "user") private Set avoirFonctions= new HashSet<>(); diff --git a/src/main/java/io/gmss/fiscad/implementations/user/UserServiceImpl.java b/src/main/java/io/gmss/fiscad/implementations/user/UserServiceImpl.java index 0aa8ce9..cd86df6 100644 --- a/src/main/java/io/gmss/fiscad/implementations/user/UserServiceImpl.java +++ b/src/main/java/io/gmss/fiscad/implementations/user/UserServiceImpl.java @@ -19,6 +19,7 @@ import io.gmss.fiscad.service.EntityFromPayLoadService; import io.gmss.fiscad.service.MailService; import io.gmss.fiscad.service.StringService; import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.authentication.AuthenticationManager; @@ -28,10 +29,12 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; -@AllArgsConstructor +//@AllArgsConstructor @Service public class UserServiceImpl implements UserService { @@ -45,7 +48,25 @@ public class UserServiceImpl implements UserService { private final StringService stringService ; private final MailService mailService ; + @Value("${dgi.sigibe-foncier.reset-pw.url}") + private String reseturl ; + @Value("${dgi.sigibe-foncier.reset-pw.token-delay}") + private static long tokenDelay ; + + + + public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, RoleService roleService, AuthenticationManager authenticationManager, TokenAuthentificationProvider tokenAuthentificationProvider, StructureService structureService, EntityFromPayLoadService entityFromPayLoadService, StringService stringService, MailService mailService) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.roleService = roleService; + this.authenticationManager = authenticationManager; + this.tokenAuthentificationProvider = tokenAuthentificationProvider; + this.structureService = structureService; + this.entityFromPayLoadService = entityFromPayLoadService; + this.stringService = stringService; + this.mailService = mailService; + } @Override @@ -61,8 +82,19 @@ public class UserServiceImpl implements UserService { User user = entityFromPayLoadService.getUserFromPayLoadWeb(userPaylaodWeb); user.setPassword(passwordEncoder.encode(userPaylaodWeb.getPassword())); + + user.setTokenDate(LocalDateTime.now()); + String token= stringService.getUniqueString(); + user.setToken(token); userRepository.save(user); + + + mailService.sendValidationMail(user.getEmail(), + user.getPrenom() + " " + user.getNom(), + user.getUsername(), + reseturl + token); + return userRepository.findUserToDtoById(userPaylaodWeb.getId()).orElse(null); } @@ -208,10 +240,7 @@ public class UserServiceImpl implements UserService { @Override public User disactivateUser(Long id) { User user = getUserById(id); - user.setActive(false); - - return userRepository.save(user); } @@ -229,6 +258,19 @@ public class UserServiceImpl implements UserService { return optionalUserPaylaodWeb.orElse(null); } + @Override + public UserPaylaodWeb resetPasswordByMail(String token, String password) { + User user = userRepository.findByToken(token).orElseThrow(() -> new NotFoundException( + String.format("Token %s invalide ou inexistant.", token) + )); + user.setPassword(passwordEncoder.encode(password)); + user.setResetPassword(false); + user= userRepository.save(user); + // mailService.sendAccountReinitMail(user,password); + Optional optionalUserPaylaodWeb = userRepository.findUserToDtoById(user.getId()); + return optionalUserPaylaodWeb.orElse(null); + } + @Override public User validateUserAccount(String username) { @@ -301,4 +343,44 @@ public class UserServiceImpl implements UserService { return userRepository.findAllUserByStructureToDtoPageable(structureId,pageable); } + @Override + public Boolean validationTokenResetPassword(String token) { + Optional optionalUser=userRepository.findByToken(token); + if(optionalUser.isEmpty()){ + return false; + } + LocalDateTime tokenDate= optionalUser.get().getTokenDate(); + if(isTokenExpired(tokenDate)){ + return false ; + } + + return true; + } + + + /** + * Calcule la date d'expiration du token en ajoutant 24h à sa date de génération. + */ + public static LocalDateTime calculerDateExpiration(LocalDateTime dateToken) { + if (dateToken == null) { + throw new IllegalArgumentException("La date du token ne peut pas être null"); + } + return dateToken.plusHours(tokenDelay); + } + + /** + * Vérifie si le token est expiré. + * Le token expire dès que la date actuelle est égale ou supérieure + * à dateToken + 24h. + */ + public static boolean isTokenExpired(LocalDateTime dateToken) { + if (dateToken == null) { + return true; + } + + LocalDateTime dateExpiration = calculerDateExpiration(dateToken); + + return !LocalDateTime.now().isBefore(dateExpiration); + } + } diff --git a/src/main/java/io/gmss/fiscad/interfaces/user/UserService.java b/src/main/java/io/gmss/fiscad/interfaces/user/UserService.java index a2d3c84..f597658 100755 --- a/src/main/java/io/gmss/fiscad/interfaces/user/UserService.java +++ b/src/main/java/io/gmss/fiscad/interfaces/user/UserService.java @@ -46,6 +46,7 @@ public interface UserService { User activateUser(Long id); User disactivateUser(Long id); UserPaylaodWeb resetPassword(String username); + UserPaylaodWeb resetPasswordByMail(String token, String password); User validateUserAccount(String username); @@ -63,4 +64,6 @@ public interface UserService { List getListUserByStructureToDto(Long structureId); Page getListUserByStructureToDto(Long structureId, Pageable pageable); + Boolean validationTokenResetPassword(String token); + } diff --git a/src/main/java/io/gmss/fiscad/paylaods/request/ResetPasswordByMailRequest.java b/src/main/java/io/gmss/fiscad/paylaods/request/ResetPasswordByMailRequest.java new file mode 100755 index 0000000..6e45143 --- /dev/null +++ b/src/main/java/io/gmss/fiscad/paylaods/request/ResetPasswordByMailRequest.java @@ -0,0 +1,12 @@ +package io.gmss.fiscad.paylaods.request; + +import io.gmss.fiscad.enums.UserRole; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class ResetPasswordByMailRequest { + private String token; + private String newPassword; +} diff --git a/src/main/java/io/gmss/fiscad/paylaods/request/record/EMailBodyRequest.java b/src/main/java/io/gmss/fiscad/paylaods/request/record/EMailBodyRequest.java new file mode 100644 index 0000000..95ac5bb --- /dev/null +++ b/src/main/java/io/gmss/fiscad/paylaods/request/record/EMailBodyRequest.java @@ -0,0 +1,19 @@ +package io.gmss.fiscad.paylaods.request.record; + +import java.util.List; + + +public record EMailBodyRequest( + List to, + List cc, + List bcc, + String from, + String fromName, + String replyTo, + String subject, + String applicantName, + String eServicesNumber, + String passwordCreationLink, + List attachments +) { +} diff --git a/src/main/java/io/gmss/fiscad/paylaods/request/record/EmailAttachmentInput.java b/src/main/java/io/gmss/fiscad/paylaods/request/record/EmailAttachmentInput.java new file mode 100644 index 0000000..6da465c --- /dev/null +++ b/src/main/java/io/gmss/fiscad/paylaods/request/record/EmailAttachmentInput.java @@ -0,0 +1,11 @@ +package io.gmss.fiscad.paylaods.request.record; + +import java.util.List; + + +public record EmailAttachmentInput( + String filename, + String contentType, + String contentBase64 +) { +} diff --git a/src/main/java/io/gmss/fiscad/paylaods/response/record/MailSendResponse.java b/src/main/java/io/gmss/fiscad/paylaods/response/record/MailSendResponse.java new file mode 100644 index 0000000..0a834c7 --- /dev/null +++ b/src/main/java/io/gmss/fiscad/paylaods/response/record/MailSendResponse.java @@ -0,0 +1,17 @@ +package io.gmss.fiscad.paylaods.response.record; + +import java.time.OffsetDateTime; +import java.util.List; + +public record MailSendResponse( + String messageId, + String status, + boolean dryRun, + String subject, + List to, + List cc, + List bcc, + int attachmentCount, + OffsetDateTime timestamp +) { +} \ No newline at end of file diff --git a/src/main/java/io/gmss/fiscad/persistence/repositories/user/UserRepository.java b/src/main/java/io/gmss/fiscad/persistence/repositories/user/UserRepository.java index 5045494..f1df747 100755 --- a/src/main/java/io/gmss/fiscad/persistence/repositories/user/UserRepository.java +++ b/src/main/java/io/gmss/fiscad/persistence/repositories/user/UserRepository.java @@ -18,6 +18,7 @@ import java.util.Set; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + Optional findByToken(String token); Optional findByEmail(String email); boolean existsByUsername(String username); diff --git a/src/main/java/io/gmss/fiscad/service/CallAPIService.java b/src/main/java/io/gmss/fiscad/service/CallAPIService.java index 26e49f2..3edcfd6 100755 --- a/src/main/java/io/gmss/fiscad/service/CallAPIService.java +++ b/src/main/java/io/gmss/fiscad/service/CallAPIService.java @@ -58,28 +58,6 @@ public class CallAPIService { } -// public RestTemplate executeRestemplate(String accessToken) { -// -// RestTemplate restTemplate = new RestTemplate(); -// -// restTemplate.getInterceptors().add((request, body, execution) -> { -// System.out.println("LE TOKEN dans execute"); -// System.out.println(accessToken); -// if (accessToken != null && !accessToken.isBlank()) { -// request.getHeaders().setBearerAuth(accessToken); -// } -// -// request.getHeaders().setAccept(List.of(MediaType.APPLICATION_JSON)); -// request.getHeaders().setContentType(MediaType.APPLICATION_JSON); -// -// return execution.execute(request, body); -// }); -// -// return restTemplate; -// } - - - public IfuEnLigneLoginResponse callGetIfuEnLigneToken() { try { @@ -121,53 +99,6 @@ public class CallAPIService { } - - -// public SygmApiResponse callPostSygmefCentre(String url, String tokenName, String accessToken, -// -// SygmApiResponse sygmApiResponse = new SygmApiResponse() ; -// try { -// HttpEntity request = new HttpEntity<>(centre); -// RestTemplate restTemplate = executeRestemplate(tokenName, accessToken); -// ResponseEntity response = restTemplate.postForEntity(url, request, SygmApiResponse.class); -// -// if(response.getStatusCodeValue()==200){ -// return response.getBody() ; -// }else{ -// sygmApiResponse.setStatut(false); -// -// sygmApiResponse.setMessage("HttpStatus "+response.getStatusCodeValue()+" --- " -// +response.getBody().toString()); -// return sygmApiResponse; -// } -// }catch (Exception e ){ -// System.out.println("SYGMEF : "+e.getMessage()) ; -// e.printStackTrace(); -// throw new Exception(e.getMessage()) ; -// } -// } - - -// public void callApiRechercheContribIfuEnLigne(IfuEnLigneRechercheBody ifuEnLigneRechercheBody) { -// try { -// String url = ifuEnLigneBaseUrl+"/api/contribuable/fiscad"; -// //ApiResponse -// RestTemplate restTemplate = executeRestemplate("Authorization",ifuEnLigneToken); -// ResponseEntity> response = restTemplate.getForEntity(url,ifuEnLigneRechercheBody, IfuEnLigneContribuableResponse.class); -// if(response.getStatusCode().value()==200){ -// System.out.println(response.getBody()); -// } -// } catch ( -// MethodNotAllowedException ex) { -// logger.error(ex.toString()); -// //return new ResponseEntity(null, HttpStatus.METHOD_NOT_ALLOWED); -// } catch (Exception e) { -// logger.error(e.toString()); -// // return new ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR); -// } -// } - - public List callApiRechercheContribIfuEnLigne( IfuEnLigneRechercheBody requestBody) { @@ -216,8 +147,6 @@ public class CallAPIService { - - // @PostConstruct private synchronized void ensureToken() { @@ -249,4 +178,9 @@ public class CallAPIService { + + + + + } diff --git a/src/main/java/io/gmss/fiscad/service/MailService.java b/src/main/java/io/gmss/fiscad/service/MailService.java index 253cfdc..87e0545 100755 --- a/src/main/java/io/gmss/fiscad/service/MailService.java +++ b/src/main/java/io/gmss/fiscad/service/MailService.java @@ -4,14 +4,19 @@ package io.gmss.fiscad.service; import io.gmss.fiscad.entities.user.User; import io.gmss.fiscad.enums.ParametersType; import io.gmss.fiscad.interfaces.ParametersRepository; +import io.gmss.fiscad.paylaods.request.record.EMailBodyRequest; +import io.gmss.fiscad.paylaods.response.record.MailSendResponse; import io.gmss.fiscad.utils.Mail; import io.gmss.fiscad.utils.MailPostmark; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import java.io.IOException; +import java.util.List; @Service public class MailService { @@ -28,8 +33,16 @@ public class MailService { @Autowired ParametersRepository parametersRepository; - // @Value("${}") - // String defaultPassWord; + @Value("${dgi.mail.validaton-compte.url}") + String urlValidationMail; + + private final RestTemplate restTemplate; + + public MailService( + RestTemplate restTemplate + ) { + this.restTemplate = restTemplate; + } @Async public void sendMail(String to, String subject, String messageContent, String helloName) { @@ -421,6 +434,10 @@ public class MailService { } + public void sendMailByUrl(User user, String passWord) { + + } + @Async public Boolean sendPostmarkMail(String to, String subject, String messageContent, String helloName, String fileName) throws IOException { String from ; @@ -432,4 +449,44 @@ public class MailService { return false ; } + + + public MailSendResponse sendValidationMail( + String recipientEmail, + String applicantName, + String eServicesNumber, + String passwordCreationLink + ) { + EMailBodyRequest request = new EMailBodyRequest( + List.of(recipientEmail), + List.of(), + List.of(), + null, + null, + null, + "[SIGIBE] Validation de votre demande d'immatriculation fiscale", + applicantName, + eServicesNumber, + passwordCreationLink, + List.of() + ); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + HttpEntity entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.exchange( + urlValidationMail, + HttpMethod.POST, + entity, + MailSendResponse.class + ); + + return response.getBody(); + } + + + } diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 816fcbb..66fd87c 100755 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -2,10 +2,16 @@ server.port=8282 io.gmss.fiscad.profile=test # LOCAL ENV TEST -spring.datasource.url=jdbc:postgresql://localhost:5432/fiscad_dgi -spring.datasource.username=infocad_user +#spring.datasource.url=jdbc:postgresql://localhost:5432/fiscad_dgi +#spring.datasource.username=infocad_user +#spring.datasource.password=W5fwD({9*q53 + + +spring.datasource.url=jdbc:postgresql://193.181.208.4:5432/fiscad_db +spring.datasource.username=fiscad_user spring.datasource.password=W5fwD({9*q53 + app.default-user.username=fiscad_admin app.default-user.password=1234567890 @@ -13,3 +19,6 @@ app.default-user.password=1234567890 ifu-en-ligne.api.base-url=https://ifubackend.impots.bj/ ifu-en-ligne.api.username=cakpona ifu-en-ligne.api.password=try + +dgi.mail.validaton-compte.url=http://novatic.vps.webdock.cloud:3308/api/emails/sigibe-validation +dgi.sigibe-foncier.reset-pw.url=https://frontend.sigibe-foncier-test.novatic.org/reset?token= diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1a0a2d9..1a74ba6 100755 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -42,6 +42,8 @@ file.jasper-reports=./jasperReport logging.file.name=/app/logs/fiscad.log + + app.upload.root=${file.upload_dir} app.upload.zips.received=${app.upload.root}/zips/received app.upload.zips.done=${app.upload.root}/zips/done @@ -78,4 +80,6 @@ logging.level.org.apache.catalina.connector.ClientAbortException=ERROR #spring.jpa.properties.hibernate.format_sql=true ##logging.level.org.hibernate.SQL=DEBUG #logging.level.org.hibernate.type.descriptor.sql=TRACE -#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE \ No newline at end of file +#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + +dgi.sigibe-foncier.reset-pw.token-delay=24 \ No newline at end of file -- 2.49.1