diff --git a/KariaMain/pom.xml b/KariaMain/pom.xml index 0283702..c61e9bd 100644 --- a/KariaMain/pom.xml +++ b/KariaMain/pom.xml @@ -89,6 +89,11 @@ + + com.twilio.sdk + twilio + 8.8.0 + tech.jhipster jhipster-framework diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/config/SecurityConfiguration.java b/KariaMain/src/main/java/com/jhipster/demo/store/config/SecurityConfiguration.java index 1e1f0c2..8132437 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/config/SecurityConfiguration.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/config/SecurityConfiguration.java @@ -79,6 +79,9 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) .pathMatchers("/api/authenticate").permitAll() .pathMatchers("/api/register").permitAll() .pathMatchers("/api/activate").permitAll() + .pathMatchers("/api/forgot-password").permitAll() + .pathMatchers("/api/validate-code").permitAll() + .pathMatchers("/api/reset-password").permitAll() .pathMatchers("/api/account/reset-password/init").permitAll() .pathMatchers("/api/account/reset-password/finish").permitAll() .pathMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepository.java b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepository.java index 1d30be0..88aa3da 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepository.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepository.java @@ -57,6 +57,7 @@ interface KariaUserRepositoryInternal { Mono findOneWithEagerRelationships(Long id); + Mono findByPhone(String phone); Flux findAllWithEagerRelationships(); Flux findAllWithEagerRelationships(Pageable page); diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepositoryInternalImpl.java b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepositoryInternalImpl.java index ee608a0..fbed297 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepositoryInternalImpl.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserRepositoryInternalImpl.java @@ -91,7 +91,10 @@ public Mono findById(Long id) { Comparison whereClause = Conditions.isEqual(entityTable.column("id"), Conditions.just(id.toString())); return createQuery(null, whereClause).one(); } - + public Mono findByPhone(String phone) { + Comparison whereClause = Conditions.isEqual(entityTable.column("phone"), Conditions.just(phone)); + return createQuery(null, whereClause).one(); + } @Override public Mono findOneWithEagerRelationships(Long id) { return findById(id); diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserSqlHelper.java b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserSqlHelper.java index 93d62f2..47b0223 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserSqlHelper.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/repository/KariaUserSqlHelper.java @@ -20,7 +20,6 @@ public static List getColumns(Table table, String columnPrefix) { columns.add(Column.aliased("address_line_2", table, columnPrefix + "_address_line_2")); columns.add(Column.aliased("city", table, columnPrefix + "_city")); columns.add(Column.aliased("role", table, columnPrefix + "_role")); - columns.add(Column.aliased("user_id", table, columnPrefix + "_user_id")); return columns; } diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/KariaUserService.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/KariaUserService.java index 9dac7dc..14daab1 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/service/KariaUserService.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/KariaUserService.java @@ -47,6 +47,7 @@ public Mono update(KariaUser kariaUser) { return kariaUserRepository.save(kariaUser); } + /** * Partially update a kariaUser. * @@ -133,7 +134,11 @@ public Mono findOne(Long id) { log.debug("Request to get KariaUser : {}", id); return kariaUserRepository.findOneWithEagerRelationships(id); } - + @Transactional(readOnly = true) + public Mono findOneByPhone(String phone) { + log.debug("Request to get KariaUser : {}", phone); + return kariaUserRepository.findByPhone(phone); + } /** * Delete the kariaUser by id. * diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/UserService.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/UserService.java index 40c21ee..f8e1c7a 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/service/UserService.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/UserService.java @@ -7,17 +7,24 @@ import com.jhipster.demo.store.domain.enumeration.Gender; import com.jhipster.demo.store.domain.enumeration.RoleEnum; import com.jhipster.demo.store.repository.AuthorityRepository; -import com.jhipster.demo.store.repository.KariaUserRepository; import com.jhipster.demo.store.repository.UserRepository; import com.jhipster.demo.store.security.AuthoritiesConstants; import com.jhipster.demo.store.security.SecurityUtils; import com.jhipster.demo.store.service.dto.AdminUserDTO; +import com.jhipster.demo.store.service.dto.PhoneVerification; import com.jhipster.demo.store.service.dto.UserDTO; + +import java.security.SecureRandom; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.twilio.Twilio; +import com.twilio.rest.api.v2010.account.Message; +import com.twilio.type.PhoneNumber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Pageable; @@ -35,8 +42,11 @@ */ @Service public class UserService { - + private final HashMap phoneCodes = new HashMap<>(); private final Logger log = LoggerFactory.getLogger(UserService.class); + private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final int CODE_LENGTH = 4; private final UserRepository userRepository; @@ -97,6 +107,69 @@ public Mono requestPasswordReset(String mail) { .flatMap(this::saveUser); } + private boolean checkPhoneNumber(String phoneNumber){ + for (int digit = 0 ; digit < phoneNumber.length(); digit++){ + if (phoneNumber.charAt(digit)< '0' || phoneNumber.charAt(digit) > '9') + return false; + } + return true; + } + @Transactional + public Mono sendMessage(String phoneNumber) { + return kariaUserService.findOneByPhone(phoneNumber).switchIfEmpty(Mono.empty()).flatMap(user ->{ + + if (user.getPhone().equals(phoneNumber) && phoneNumber.length() == 8 && checkPhoneNumber(phoneNumber)) { + Twilio.init(System.getenv("TWILIO_ACCOUNT_SID"), System.getenv("TWILIO_AUTH_TOKEN")); + StringBuilder codeBuilder = new StringBuilder(); + for (int i = 0; i < CODE_LENGTH; i++) { + int randomIndex = SECURE_RANDOM.nextInt(CHARACTERS.length()); + codeBuilder.append(CHARACTERS.charAt(randomIndex)); + } + String code = codeBuilder.toString(); + phoneCodes.put(user.getPhone(),new PhoneVerification(code)); + Message.creator(new PhoneNumber("+216"+phoneNumber), + new PhoneNumber("+16205518972"), "Your code is :" + code).create(); + return Mono.just(true); + } + return Mono.just(false); + }); + } + @Transactional + public Mono resetPassword(String phoneNumber,String password) { + AtomicBoolean isVerified = new AtomicBoolean(false); + return kariaUserService + .findOneByPhone(phoneNumber) + .flatMap(kariaUser -> { + if (phoneCodes.get(kariaUser.getPhone()).getVerified()){ + isVerified.set(true); + } + + return Mono.just(kariaUser); + }) + .flatMap(kariaUser -> { + log.debug(kariaUser.getUserId().toString()); + return userRepository.findById(kariaUser.getUserId());}) + .map(user -> { + + if (isVerified.get()) { + String encodedPassword = passwordEncoder.encode(user.getPassword()); + user.setPassword(password); + phoneCodes.remove(phoneNumber); + } + return user; + }) + .flatMap(this::saveUser) + .flatMap(user ->{ + return Mono.just(isVerified.get()); + }); + } + public boolean checkCode( String code,String phoneNumber) { + if(phoneCodes.get(phoneNumber) != null && code.equals(phoneCodes.get(phoneNumber).getCode())){ + phoneCodes.get(phoneNumber).setVerified(true); + return true; + } + return false; + } @Transactional public Mono registerUser(AdminUserDTO userDTO, String password, String phoneNumber) { KariaUser kariaUser = new KariaUser(); diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/CodeDTO.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/CodeDTO.java new file mode 100644 index 0000000..154ae6f --- /dev/null +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/CodeDTO.java @@ -0,0 +1,30 @@ +package com.jhipster.demo.store.service.dto; + +import java.io.Serializable; + +public class CodeDTO implements Serializable { + private static final long serialVersionUID = 1L; + private String code; + private String phoneNumber; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public CodeDTO(String code, String phoneNumber) { + this.code = code; + this.phoneNumber = phoneNumber; + } +} diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/MessageDTO.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/MessageDTO.java new file mode 100644 index 0000000..4a19528 --- /dev/null +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/MessageDTO.java @@ -0,0 +1,24 @@ +package com.jhipster.demo.store.service.dto; + +public class MessageDTO { + private String message; + + private int status; + public String getMessage() { + return message; + } + public int getStatus(){ + return status; + } + + public void setMessage(String message) { + this.message = message; + } + private void setStatus(int status){ + this.status = status; + } + public MessageDTO(String message,int status) { + this.message = message; + this.status = status; + } +} diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PasswordDTO.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PasswordDTO.java new file mode 100644 index 0000000..264c409 --- /dev/null +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PasswordDTO.java @@ -0,0 +1,29 @@ +package com.jhipster.demo.store.service.dto; + +import java.io.Serializable; + +public class PasswordDTO implements Serializable { + private String password; + private String phoneNumber; + + public PasswordDTO(String password, String phoneNumber) { + this.password = password; + this.phoneNumber = phoneNumber; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } +} diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneNumberDTO.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneNumberDTO.java new file mode 100644 index 0000000..b7d4354 --- /dev/null +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneNumberDTO.java @@ -0,0 +1,26 @@ +package com.jhipster.demo.store.service.dto; + +import java.io.Serializable; + +public class PhoneNumberDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String phoneNumber; + + public PhoneNumberDTO() { + // Empty constructor needed for Jackson. + } + + public PhoneNumberDTO(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } +} diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneVerification.java b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneVerification.java new file mode 100644 index 0000000..fd5ddd8 --- /dev/null +++ b/KariaMain/src/main/java/com/jhipster/demo/store/service/dto/PhoneVerification.java @@ -0,0 +1,26 @@ +package com.jhipster.demo.store.service.dto; + +import java.io.Serializable; + +public class PhoneVerification implements Serializable { + private String code; + private boolean verified = false; + + public PhoneVerification(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + public boolean getVerified(){ + return this.verified; + } + public void setVerified(boolean verified){ + this.verified = verified; + } +} diff --git a/KariaMain/src/main/java/com/jhipster/demo/store/web/rest/AccountResource.java b/KariaMain/src/main/java/com/jhipster/demo/store/web/rest/AccountResource.java index 526626d..74f24fd 100644 --- a/KariaMain/src/main/java/com/jhipster/demo/store/web/rest/AccountResource.java +++ b/KariaMain/src/main/java/com/jhipster/demo/store/web/rest/AccountResource.java @@ -1,20 +1,22 @@ package com.jhipster.demo.store.web.rest; +import com.jhipster.demo.store.domain.User; import com.jhipster.demo.store.repository.UserRepository; import com.jhipster.demo.store.security.SecurityUtils; import com.jhipster.demo.store.service.MailService; import com.jhipster.demo.store.service.UserService; -import com.jhipster.demo.store.service.dto.AdminUserDTO; -import com.jhipster.demo.store.service.dto.PasswordChangeDTO; +import com.jhipster.demo.store.service.dto.*; import com.jhipster.demo.store.web.rest.errors.*; import com.jhipster.demo.store.web.rest.vm.KeyAndPasswordVM; import com.jhipster.demo.store.web.rest.vm.ManagedUserVM; +import jakarta.mail.Message; import jakarta.validation.Valid; import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; @@ -140,7 +142,28 @@ public Mono changePassword(@RequestBody PasswordChangeDTO passwordChangeDt } return userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword()); } + @PostMapping(path = "/reset-password") + public Mono> changePassword(@RequestBody PasswordDTO passwordDTO) { + if (isPasswordLengthInvalid(passwordDTO.getPassword())) { + throw new InvalidPasswordException(); + } + return userService.resetPassword(passwordDTO.getPhoneNumber(),passwordDTO.getPassword()) + .map(bool -> ResponseEntity.status(bool ? 200 : 400) + .body(new MessageDTO (bool ? "Password reset successfully" : "error while resetting password",bool ? 200 : 400))); + } + @PostMapping(path = "/forgot-password") + public Mono> forgotPassword(@RequestBody PhoneNumberDTO phoneNumberDTO) { + return userService.sendMessage(phoneNumberDTO.getPhoneNumber()) + .thenReturn("Password reset instructions sent to " + phoneNumberDTO.getPhoneNumber()) + .map(message -> ResponseEntity.ok().body(new MessageDTO(message,200))) + .defaultIfEmpty(ResponseEntity.badRequest().body(new MessageDTO("Failed to send password reset instructions",400))); + } + @PostMapping(path = "/validate-code") + public ResponseEntity validateCode(@RequestBody CodeDTO codeDTO) { + boolean bool = userService.checkCode(codeDTO.getCode(),codeDTO.getPhoneNumber()); + return ResponseEntity.status(bool ? 200 : 400).body(new MessageDTO(bool ? "Code is correct !" : "Invalid code!",bool ? 200 : 400)); + } /** * {@code POST /account/reset-password/init} : Send an email to reset the password of the user. *