Skip to content

Commit

Permalink
AYS-318 | Forgot Password Flow Has Been Created (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
agitrubard authored Jul 18, 2024
1 parent f0e0170 commit f08ed0e
Show file tree
Hide file tree
Showing 22 changed files with 584 additions and 54 deletions.
34 changes: 26 additions & 8 deletions src/main/java/org/ays/auth/controller/AysAuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.ays.auth.model.AysToken;
import org.ays.auth.model.mapper.AysTokenToAysTokenResponseMapper;
import org.ays.auth.model.mapper.AysTokenToResponseMapper;
import org.ays.auth.model.request.AysForgotPasswordRequest;
import org.ays.auth.model.request.AysLoginRequest;
import org.ays.auth.model.request.AysTokenInvalidateRequest;
import org.ays.auth.model.request.AysTokenRefreshRequest;
import org.ays.auth.model.response.AysTokenResponse;
import org.ays.auth.service.AysAuthService;
import org.ays.auth.service.AysUserPasswordService;
import org.ays.common.model.response.AysResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -23,10 +25,11 @@
@RequiredArgsConstructor
class AysAuthController {

private final AysAuthService userAuthService;
private final AysAuthService authService;
private final AysUserPasswordService userPasswordService;


private final AysTokenToAysTokenResponseMapper aysTokenToAysTokenResponseMapper = AysTokenToAysTokenResponseMapper.initialize();
private final AysTokenToResponseMapper tokenToTokenResponseMapper = AysTokenToResponseMapper.initialize();


/**
Expand All @@ -37,11 +40,12 @@ class AysAuthController {
*/
@PostMapping("/token")
public AysResponse<AysTokenResponse> landingAuthenticate(@RequestBody @Valid AysLoginRequest loginRequest) {
final AysToken token = userAuthService.authenticate(loginRequest);
final AysTokenResponse tokenResponse = aysTokenToAysTokenResponseMapper.map(token);
final AysToken token = authService.authenticate(loginRequest);
final AysTokenResponse tokenResponse = tokenToTokenResponseMapper.map(token);
return AysResponse.successOf(tokenResponse);
}


/**
* This endpoint allows user to refresh token.
*
Expand All @@ -50,11 +54,12 @@ public AysResponse<AysTokenResponse> landingAuthenticate(@RequestBody @Valid Ays
*/
@PostMapping("/token/refresh")
public AysResponse<AysTokenResponse> refreshToken(@RequestBody @Valid AysTokenRefreshRequest refreshRequest) {
final AysToken token = userAuthService.refreshAccessToken(refreshRequest.getRefreshToken());
final AysTokenResponse tokenResponse = aysTokenToAysTokenResponseMapper.map(token);
final AysToken token = authService.refreshAccessToken(refreshRequest.getRefreshToken());
final AysTokenResponse tokenResponse = tokenToTokenResponseMapper.map(token);
return AysResponse.successOf(tokenResponse);
}


/**
* Endpoint for invalidating a token. Only users with the 'USER' authority are allowed to access this endpoint.
* It invalidates the access token and refresh token associated with the provided refresh token.
Expand All @@ -64,7 +69,20 @@ public AysResponse<AysTokenResponse> refreshToken(@RequestBody @Valid AysTokenRe
*/
@PostMapping("/token/invalidate")
public AysResponse<Void> invalidateTokens(@RequestBody @Valid AysTokenInvalidateRequest invalidateRequest) {
userAuthService.invalidateTokens(invalidateRequest.getRefreshToken());
authService.invalidateTokens(invalidateRequest.getRefreshToken());
return AysResponse.SUCCESS;
}


/**
* This endpoint allows a user to request a password create.
*
* @param forgotPasswordRequest An AysForgotPasswordRequest object containing the user's email address.
* @return An AysResponse indicating the success of the password create request.
*/
@PostMapping("/password/forgot")
public AysResponse<Void> forgotPassword(@RequestBody @Valid AysForgotPasswordRequest forgotPasswordRequest) {
userPasswordService.forgotPassword(forgotPasswordRequest);
return AysResponse.SUCCESS;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/ays/auth/model/AysUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void notVerify() {
@EqualsAndHashCode(callSuper = true)
public static class Password extends BaseDomainModel {

private Long id;
private String id;
private String value;

}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/ays/auth/model/entity/AysUserEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ public static class PasswordEntity extends BaseEntity {

@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@GeneratedValue(strategy = GenerationType.UUID)
private String id;

@Column(name = "VALUE")
private String value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public enum AysConfigurationParameter {
AUTH_ACCESS_TOKEN_EXPIRE_MINUTE("120"),
AUTH_REFRESH_TOKEN_EXPIRE_DAY("1"),
AUTH_TOKEN_PRIVATE_KEY(""),
AUTH_TOKEN_PUBLIC_KEY("");
AUTH_TOKEN_PUBLIC_KEY(""),
FE_URL("http://localhost:3000");

private final String defaultValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@
import org.mapstruct.factory.Mappers;

/**
* {@link AysTokenToAysTokenResponseMapper} is an interface that defines the mapping between a {@link AysToken} and an {@link AysTokenResponse}.
* {@link AysTokenToResponseMapper} is an interface that defines the mapping between a {@link AysToken} and an {@link AysTokenResponse}.
* This interface uses the MapStruct annotation @Mapper to generate an implementation of this interface at compile-time.
* <p>The class provides a static method {@code initialize()} that returns an instance of the generated mapper implementation.
* <p>The interface extends the MapStruct interface {@link BaseMapper}, which defines basic mapping methods.
* The interface adds no additional mapping methods, but simply defines the types to be used in the mapping process.
*/
@Mapper
public interface AysTokenToAysTokenResponseMapper extends BaseMapper<AysToken, AysTokenResponse> {
public interface AysTokenToResponseMapper extends BaseMapper<AysToken, AysTokenResponse> {

/**
* Initializes the mapper.
*
* @return the initialized mapper object.
*/
static AysTokenToAysTokenResponseMapper initialize() {
return Mappers.getMapper(AysTokenToAysTokenResponseMapper.class);
static AysTokenToResponseMapper initialize() {
return Mappers.getMapper(AysTokenToResponseMapper.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.ays.auth.model.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.ays.common.util.validation.Email;

@Getter
@Setter
public class AysForgotPasswordRequest {

@Email
@NotBlank
@Size(min = 2, max = 255)
private String emailAddress;

}
4 changes: 2 additions & 2 deletions src/main/java/org/ays/auth/port/impl/AysUserAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ public AysUser save(final AysUser user) {
userEntity.getLoginAttempt().setUser(userEntity);
}

userRepository.save(userEntity);
return userEntityToDomainMapper.map(userEntity);
final AysUserEntity savedUserEntity = userRepository.save(userEntity);
return userEntityToDomainMapper.map(savedUserEntity);
}

}
19 changes: 19 additions & 0 deletions src/main/java/org/ays/auth/service/AysUserPasswordService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.ays.auth.service;

import org.ays.auth.model.request.AysForgotPasswordRequest;

/**
* Service interface for handling user password operations.
* Implementations of this interface should provide functionality for handling forgotten passwords.
*/
public interface AysUserPasswordService {

/**
* Handles the forgot password request by sending an email to the user
* with instructions to create a new password.
*
* @param forgotPasswordRequest the request containing the user's email address.
*/
void forgotPassword(AysForgotPasswordRequest forgotPasswordRequest);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.ays.auth.service.impl;

import lombok.RequiredArgsConstructor;
import org.ays.auth.model.AysUser;
import org.ays.auth.model.enums.AysConfigurationParameter;
import org.ays.auth.model.request.AysForgotPasswordRequest;
import org.ays.auth.port.AysUserReadPort;
import org.ays.auth.port.AysUserSavePort;
import org.ays.auth.service.AysUserPasswordService;
import org.ays.auth.util.exception.AysEmailAddressNotValidException;
import org.ays.common.model.AysMail;
import org.ays.common.model.enums.AysMailTemplate;
import org.ays.common.service.AysMailService;
import org.ays.common.util.AysRandomUtil;
import org.ays.parameter.model.AysParameter;
import org.ays.parameter.port.AysParameterReadPort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

/**
* Service implementation for handling user password operations such as forgotten password.
* This service handles the retrieval of user information and sending emails for password creation.
*/
@Service
@Transactional
@RequiredArgsConstructor
class AysUserPasswordServiceImpl implements AysUserPasswordService {

private final AysUserReadPort userReadPort;
private final AysUserSavePort userSavePort;
private final AysMailService mailService;
private final AysParameterReadPort parameterReadPort;

/**
* Handles the forgot password request by sending an email to the user
* with instructions to create a new password.
*
* @param forgotPasswordRequest the request containing the user's email address.
* @throws AysEmailAddressNotValidException if the email address is not associated with any user.
*/
@Override
public void forgotPassword(final AysForgotPasswordRequest forgotPasswordRequest) {

final String emailAddress = forgotPasswordRequest.getEmailAddress();
final AysUser user = userReadPort.findByEmailAddress(emailAddress)
.orElseThrow(() -> new AysEmailAddressNotValidException(emailAddress));

if (user.getPassword() != null) {
this.sendPasswordCreateEmail(user);
return;
}

final AysUser.Password password = AysUser.Password.builder()
.value(AysRandomUtil.generateUUID())
.build();
user.setPassword(password);
AysUser savedUser = userSavePort.save(user);

this.sendPasswordCreateEmail(savedUser);
}

/**
* Sends an email to the user with instructions to create a new password.
*
* @param user the user to whom the email should be sent.
*/
private void sendPasswordCreateEmail(final AysUser user) {

final Map<String, Object> parameters = Map.of(
"userFullName", user.getFirstName() + " " + user.getLastName(),
"url", this.findFeUrl().concat("/create-password/").concat(user.getPassword().getId())
);

final AysMail mail = AysMail.builder()
.to(List.of(user.getEmailAddress()))
.template(AysMailTemplate.CREATE_PASSWORD)
.parameters(parameters)
.build();

mailService.send(mail);
}

/**
* Retrieves the Front-End URL from the configuration parameters.
*
* @return the Front-End URL.
*/
private String findFeUrl() {
return parameterReadPort
.findByName(AysConfigurationParameter.FE_URL.name())
.orElse(AysParameter.from(AysConfigurationParameter.FE_URL))
.getDefinition();
}

}
2 changes: 2 additions & 0 deletions src/main/java/org/ays/common/model/AysMail.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.ays.common.model.enums.AysMailTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Getter
@Setter
@Builder
public class AysMail {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
public enum AysMailTemplate {

@Deprecated(forRemoval = true, since = "This value will be removed, when new values are added to the enum.")
EXAMPLE("example.html");
EXAMPLE("create-password.html"),
CREATE_PASSWORD("create-password.html");

private final String file;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -66,6 +67,8 @@ private MimeMessage createMimeMessage(final AysMail mail) throws IOException, Me
String htmlContentWithParameters = this.addParameters(htmlContent, mail.getParameters());
mimeMessage.setText(htmlContentWithParameters, "UTF-8", "html");

mimeMessage.setFrom(new InternetAddress("[email protected]", "Afet Yönetim Sistemi"));

for (String to : mail.getTo()) {
mimeMessage.addRecipients(Message.RecipientType.TO, to);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/db/changelog/changes/1-ays-ddl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@
<!-- DDL of AYS_USER_PASSWORD -->
<!-- ======================== -->
<createTable tableName="AYS_USER_PASSWORD">
<column name="ID" type="BIGINT" autoIncrement="true">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="USER_ID" type="VARCHAR(36)">
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/db/changelog/changes/2-ays-dml.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>
<insert tableName="AYS_PARAMETER">
<column name="NAME" value="FE_URL"/>
<column name="DEFINITION" value="http://localhost:3000"/>
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>
<!-- ==================== -->
<!-- DML of AYS_PARAMETER -->
<!-- ==================== -->
Expand Down Expand Up @@ -357,32 +363,37 @@
<!-- DML of AYS_USER_PASSWORD -->
<!-- ======================== -->
<insert tableName="AYS_USER_PASSWORD">
<column name="ID" value="41cc85c1-2500-4bd0-8682-a48ab63c0b1b"/>
<column name="USER_ID" value="f882cb9c-9341-473b-a040-3fbd05c09ac6"/>
<column name="VALUE" value="$2a$10$16pFBczPxydfiRS4whgKfOCxq58L.bB6.i2abkZKR4fpNleQ4SmDy"/>
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>

<insert tableName="AYS_USER_PASSWORD">
<column name="ID" value="7e5cafbd-f9d2-4e4d-8254-35dc45c5ff03"/>
<column name="USER_ID" value="9ebcd692-fc0b-4f76-9948-3dd246d73758"/>
<column name="VALUE" value="$2a$10$16pFBczPxydfiRS4whgKfOCxq58L.bB6.i2abkZKR4fpNleQ4SmDy"/>
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>
<insert tableName="AYS_USER_PASSWORD">
<column name="ID" value="be7e26fd-dd85-4e14-8004-72dd1b87b206"/>
<column name="USER_ID" value="cf1587fd-f800-42f2-ac6e-bd7b1c4d993d"/>
<column name="VALUE" value="$2a$10$16pFBczPxydfiRS4whgKfOCxq58L.bB6.i2abkZKR4fpNleQ4SmDy"/>
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>

<insert tableName="AYS_USER_PASSWORD">
<column name="ID" value="631a1af5-edb4-46c1-b59e-e70a93cbd4b2"/>
<column name="USER_ID" value="5160acee-a1ba-46c0-a555-12637b2ac7fc"/>
<column name="VALUE" value="$2a$10$16pFBczPxydfiRS4whgKfOCxq58L.bB6.i2abkZKR4fpNleQ4SmDy"/>
<column name="CREATED_USER" value="AYS"/>
<column name="CREATED_AT" valueDate="now()"/>
</insert>
<insert tableName="AYS_USER_PASSWORD">
<column name="ID" value="9b4196fc-5f83-4b9e-a0d1-a53daea4e8f6"/>
<column name="USER_ID" value="1be3ff7c-8f72-4f25-bbba-6db030f61629"/>
<column name="VALUE" value="$2a$10$16pFBczPxydfiRS4whgKfOCxq58L.bB6.i2abkZKR4fpNleQ4SmDy"/>
<column name="CREATED_USER" value="AYS"/>
Expand Down
Loading

0 comments on commit f08ed0e

Please sign in to comment.