-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MAIN-T-117 Create endpoint to send Restore Password email (#92)
* Add password reset feature with token generation and email notification - Implemented a password reset functionality that checks user existence, generates a reset password token, and sends an email notification with the reset password token. - Created relevant test cases and updated user service and user facade. - Added endpoint to reset password and updated JwtAuthorizationFilter for password reset endpoint. - Included new database table for storing reset password tokens. * Add logging and exception handling to reset password process In the PasswordFacade class, a log statement and exception handling have been added to the reset password function. Now, when the password reset email is sent, it's wrapped in a try/catch block to handle any potential exceptions. Additionally, a debug-level log is generated whenever a rest password token is created. * Refactor EmailService and remove unused variables Refactored the EmailService class to extract redundant code into a separate 'sendEmail' function. This makes the code easier to read and maintain. Additionally, removed unused variables in 'restore-password.html' and 'PasswordSpec.kt'.
- Loading branch information
1 parent
8f3b7a0
commit 463a396
Showing
29 changed files
with
563 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,3 +44,4 @@ bin | |
|
||
/doky-*.env | ||
/logs/ | ||
/.run/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package org.hkurh.doky | ||
|
||
import io.restassured.RestAssured.given | ||
import org.hkurh.doky.password.api.ResetPasswordRequest | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
import org.springframework.http.HttpStatus | ||
|
||
@DisplayName("Password API test") | ||
class PasswordSpec : RestSpec() { | ||
|
||
private val endpoint = "/password" | ||
private val resetEndpoint = "$endpoint/reset" | ||
private val nonExistUserEmail = "[email protected]" | ||
|
||
|
||
@Test | ||
@DisplayName("Should return Not Fount if no user with provided email") | ||
fun shouldReturnNotFount_whenNoUserWithProvidedEmail() { | ||
// given | ||
val requestBody = ResetPasswordRequest().apply { | ||
email = nonExistUserEmail | ||
} | ||
val requestSpec = prepareRequestSpec().setBody(requestBody).build() | ||
|
||
// when | ||
val response = given(requestSpec).post(resetEndpoint) | ||
|
||
// then | ||
response.then().statusCode(HttpStatus.NOT_FOUND.value()) | ||
} | ||
|
||
@Test | ||
@DisplayName("Should process if user with provided email exists") | ||
fun shouldProcess_whenUserExists() { | ||
// given | ||
val requestBody = ResetPasswordRequest().apply { | ||
email = validUserUid | ||
} | ||
val requestSpec = prepareRequestSpec().setBody(requestBody).build() | ||
|
||
// when | ||
val response = given(requestSpec).post(resetEndpoint) | ||
|
||
// then | ||
response.then().statusCode(HttpStatus.NO_CONTENT.value()) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
insert into document (name, description, file_name, created_date, modified_date, creator_id ) | ||
replace into document (name, description, file_name, created_date, modified_date, creator_id ) | ||
select "Test_1", "That is a test document", "test.txt", '2024-01-15 13:00:00', '2024-01-15 13:00:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Test_2", "That is a second test document", '2024-01-15 16:00:00', '2024-01-15 16:00:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); | ||
replace into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Test_3", "That is a test document", '2024-02-13 13:00:00', '2024-03-15 13:24:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Test_4", "That is a second test document", '2024-01-15 13:00:00', '2024-01-15 13:00:00', u.id from user u where u.uid = "[email protected]"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
insert into document (name, description, file_name, created_date, modified_date, creator_id ) | ||
replace into document (name, description, file_name, created_date, modified_date, creator_id ) | ||
select "Test note 1", "That is a test document", "test.txt", '2024-01-15 13:00:00', '2024-01-15 13:00:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Lorem", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae augue et tortor dapibus finibus.", '2024-01-15 16:00:00', '2024-01-15 16:00:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); | ||
replace into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Test note 2", "That is a test document", '2024-02-13 13:00:00', '2024-03-15 13:24:00', u.id from user u where u.uid = "[email protected]"; | ||
|
||
insert into document (name, description, created_date, modified_date, creator_id ) | ||
replace into document (name, description, created_date, modified_date, creator_id ) | ||
select "Cras at nulla ex", "Phasellus vestibulum nisl augue, a pharetra nunc molestie ut. Integer mollis ex fringilla vulputate facilisis.", '2024-01-15 13:00:00', '2024-01-15 13:00:00', u.id from user u where u.uid = "[email protected]"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
delete d from document d inner join user u on d.creator_id = u.id where u.uid = "[email protected]"; | ||
|
||
delete from reset_password_token; | ||
|
||
delete u from user u where u.uid = "[email protected]"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
insert into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); | ||
replace into user (uid, name, password) values ( "[email protected]", "Hanna", "$2a$10$bdZSuBncZqaM4XwHcjxbpeQuXeOxk6vCEsFrTwa91xh3M3JpvW41m" ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
server/src/main/java/org/hkurh/doky/password/PasswordFacade.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.hkurh.doky.password | ||
|
||
import org.hkurh.doky.email.EmailService | ||
import org.hkurh.doky.errorhandling.DokyNotFoundException | ||
import org.hkurh.doky.users.UserService | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class PasswordFacade( | ||
private val userService: UserService, | ||
private val resetPasswordService: ResetPasswordService, | ||
private val emailService: EmailService | ||
) { | ||
|
||
fun reset(email: String) { | ||
if (!userService.exists(email)) throw DokyNotFoundException("User does not exist") | ||
|
||
val user = userService.findUserByUid(email) | ||
val token = resetPasswordService.generateAndSaveResetToken(user!!) | ||
LOG.debug("Generate reset password token for user ${user.id}") | ||
try { | ||
emailService.sendRestorePasswordEmail(user, token) | ||
} catch (e: Exception) { | ||
LOG.error("Error sending reset password email for user ${user.id}", e) | ||
} | ||
} | ||
|
||
|
||
companion object { | ||
private val LOG = LoggerFactory.getLogger(PasswordFacade::class.java) | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
server/src/main/java/org/hkurh/doky/password/ResetPasswordService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.hkurh.doky.password | ||
|
||
import org.hkurh.doky.password.db.ResetPasswordTokenEntity | ||
import org.hkurh.doky.password.db.ResetPasswordTokenRepository | ||
import org.hkurh.doky.users.db.UserEntity | ||
import org.springframework.stereotype.Service | ||
|
||
@Service | ||
class ResetPasswordService( | ||
private var tokenUtil: TokenUtil, | ||
private val resetPasswordTokenRepository: ResetPasswordTokenRepository | ||
) { | ||
|
||
fun generateAndSaveResetToken(user: UserEntity): String { | ||
resetPasswordTokenRepository.findByUser(user)?.let { | ||
resetPasswordTokenRepository.delete(it) | ||
} | ||
val token = tokenUtil.generateToken() | ||
val expirationDate = tokenUtil.calculateExpirationDate() | ||
val resetPasswordTokenEntity = ResetPasswordTokenEntity().apply { | ||
this.token = token | ||
this.user = user | ||
this.expirationDate = expirationDate | ||
} | ||
val savedPasswordTokenEntity = resetPasswordTokenRepository.save(resetPasswordTokenEntity) | ||
return savedPasswordTokenEntity.token!! | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.hkurh.doky.password | ||
|
||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.stereotype.Component | ||
import java.time.Instant | ||
import java.time.LocalDateTime | ||
import java.time.ZoneId | ||
import java.time.ZoneOffset | ||
import java.util.* | ||
|
||
@Component | ||
class TokenUtil { | ||
|
||
private val zoneId = ZoneId.of("UTC") | ||
|
||
@Value("\${doky.password.reset.token.duration}") | ||
var resetTokenDuration: Long = 10 | ||
|
||
fun calculateExpirationDate(): Date { | ||
val currentDate = LocalDateTime.ofInstant(Instant.now(), zoneId) | ||
val expiredDate = currentDate.plusMinutes(resetTokenDuration) | ||
return Date.from(expiredDate.toInstant(ZoneOffset.UTC)) | ||
} | ||
|
||
fun generateToken() : String { | ||
return UUID.randomUUID().toString() | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
server/src/main/java/org/hkurh/doky/password/api/PasswordApi.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.hkurh.doky.password.api | ||
|
||
import io.swagger.v3.oas.annotations.Operation | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses | ||
import io.swagger.v3.oas.annotations.security.SecurityRequirement | ||
import io.swagger.v3.oas.annotations.tags.Tag | ||
import jakarta.validation.constraints.NotBlank | ||
import jakarta.validation.constraints.Pattern | ||
import jakarta.validation.constraints.Size | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.web.bind.annotation.RequestBody | ||
|
||
@Tag(name = "Password") | ||
@SecurityRequirement(name = "Bearer Token") | ||
interface PasswordApi { | ||
|
||
|
||
@Operation(summary = "Send a request (email) to restore password for user") | ||
@ApiResponses( | ||
ApiResponse(responseCode = "204", description = "Reset password request is applied successfully"), | ||
ApiResponse(responseCode = "404", description = "There no registered user with provided email"), | ||
ApiResponse(responseCode = "400", description = "Provided email is null, empty or has incorrect format") | ||
) | ||
fun reset(@RequestBody resetPasswordRequest: ResetPasswordRequest): ResponseEntity<Any> | ||
} |
22 changes: 22 additions & 0 deletions
22
server/src/main/java/org/hkurh/doky/password/api/PasswordController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.hkurh.doky.password.api | ||
|
||
import jakarta.validation.Valid | ||
import org.hkurh.doky.password.PasswordFacade | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.web.bind.annotation.PostMapping | ||
import org.springframework.web.bind.annotation.RequestBody | ||
import org.springframework.web.bind.annotation.RequestMapping | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestController | ||
@RequestMapping("/password") | ||
class PasswordController( | ||
val passwordFacade: PasswordFacade | ||
) : PasswordApi { | ||
|
||
@PostMapping("/reset") | ||
override fun reset(@RequestBody @Valid resetPasswordRequest: ResetPasswordRequest): ResponseEntity<Any> { | ||
passwordFacade.reset(resetPasswordRequest.email) | ||
return ResponseEntity.noContent().build() | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
server/src/main/java/org/hkurh/doky/password/api/ResetPasswordRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package org.hkurh.doky.password.api | ||
|
||
import jakarta.validation.constraints.NotBlank | ||
import jakarta.validation.constraints.Pattern | ||
import jakarta.validation.constraints.Size | ||
|
||
class ResetPasswordRequest { | ||
@NotBlank(message = "Email is required") | ||
@Size(min = 4, max = 32, message = "Length should be from 4 to 32 characters") | ||
@Pattern( | ||
regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}\$", | ||
message = "Should be an valid email address" | ||
) | ||
var email: String = "" | ||
} |
38 changes: 38 additions & 0 deletions
38
server/src/main/java/org/hkurh/doky/password/db/ResetPasswordTokenEntity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.hkurh.doky.password.db | ||
|
||
import jakarta.persistence.Column | ||
import jakarta.persistence.Entity | ||
import jakarta.persistence.GeneratedValue | ||
import jakarta.persistence.GenerationType | ||
import jakarta.persistence.Id | ||
import jakarta.persistence.Index | ||
import jakarta.persistence.JoinColumn | ||
import jakarta.persistence.OneToOne | ||
import jakarta.persistence.Table | ||
import jakarta.persistence.UniqueConstraint | ||
import org.hkurh.doky.users.db.UserEntity | ||
import java.util.* | ||
|
||
@Entity | ||
@Table( | ||
name = "reset_password_token", | ||
indexes = [Index(name = "idx_reset_password_token_token", columnList = "token")], | ||
uniqueConstraints = [UniqueConstraint(name = "uq_reset_password_token_token", columnNames = ["token"])] | ||
) | ||
class ResetPasswordTokenEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "id", nullable = false) | ||
var id: Long = -1 | ||
|
||
@OneToOne | ||
@JoinColumn(name = "user") | ||
var user: UserEntity? = null | ||
|
||
@Column(name = "token") | ||
var token: String? = null | ||
|
||
@Column(name = "expiration_date") | ||
var expirationDate: Date? = null | ||
} |
Oops, something went wrong.