Skip to content

Commit

Permalink
Add password reset field verification (halo-dev#1636)
Browse files Browse the repository at this point in the history
* perf: add password reset field verification

Signed-off-by: Ryan Wang <[email protected]>

* feat: add unit test case

Co-authored-by: guqing <[email protected]>
  • Loading branch information
ruibaby and guqing authored Feb 20, 2022
1 parent acf6a98 commit 1ee7b58
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import run.halo.app.model.enums.MFAType;
import run.halo.app.model.params.LoginParam;
import run.halo.app.model.params.ResetPasswordParam;
import run.halo.app.model.params.ResetPasswordSendCodeParam;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.security.token.AuthToken;
Expand Down Expand Up @@ -80,7 +81,7 @@ public void logout() {
@ApiOperation("Sends reset password verify code")
@CacheLock(autoDelete = false)
@DisableOnCondition
public void sendResetCode(@RequestBody @Valid ResetPasswordParam param) {
public void sendResetCode(@RequestBody @Valid ResetPasswordSendCodeParam param) {
adminService.sendResetPasswordCode(param);
}

Expand Down
14 changes: 7 additions & 7 deletions src/main/java/run/halo/app/model/params/ResetPasswordParam.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package run.halo.app.model.params;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* Reset password params.
Expand All @@ -10,15 +12,13 @@
* @date 2019-09-05
*/
@Data
public class ResetPasswordParam {

@NotBlank(message = "用户名不能为空")
private String username;

@NotBlank(message = "邮箱不能为空")
private String email;
@EqualsAndHashCode(callSuper = true)
public class ResetPasswordParam extends ResetPasswordSendCodeParam {

@NotBlank(message = "验证码不能为空")
private String code;

@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 100, message = "密码的字符长度必须在 {min} - {max} 之间")
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package run.halo.app.model.params;

import javax.validation.constraints.NotBlank;
import lombok.Data;

/**
* Parameters required to send the reset password verification code.
*
* @author ryanwang
* @date 2022-01-24
*/
@Data
public class ResetPasswordSendCodeParam {

@NotBlank(message = "用户名不能为空")
private String username;

@NotBlank(message = "邮箱不能为空")
private String email;
}
3 changes: 2 additions & 1 deletion src/main/java/run/halo/app/service/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import run.halo.app.model.entity.User;
import run.halo.app.model.params.LoginParam;
import run.halo.app.model.params.ResetPasswordParam;
import run.halo.app.model.params.ResetPasswordSendCodeParam;
import run.halo.app.security.token.AuthToken;

/**
Expand Down Expand Up @@ -54,7 +55,7 @@ public interface AdminService {
*
* @param param param must not be null
*/
void sendResetPasswordCode(@NonNull ResetPasswordParam param);
void sendResetPasswordCode(@NonNull ResetPasswordSendCodeParam param);

/**
* Reset password by code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import run.halo.app.model.enums.MFAType;
import run.halo.app.model.params.LoginParam;
import run.halo.app.model.params.ResetPasswordParam;
import run.halo.app.model.params.ResetPasswordSendCodeParam;
import run.halo.app.model.properties.EmailProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.security.authentication.Authentication;
Expand Down Expand Up @@ -184,7 +185,7 @@ public void clearToken() {
}

@Override
public void sendResetPasswordCode(ResetPasswordParam param) {
public void sendResetPasswordCode(ResetPasswordSendCodeParam param) {
cacheStore.getAny("code", String.class).ifPresent(code -> {
throw new ServiceException("已经获取过验证码,不能重复获取");
});
Expand Down
203 changes: 203 additions & 0 deletions src/test/java/run/halo/app/controller/AdminControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package run.halo.app.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.nio.charset.StandardCharsets;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import run.halo.app.controller.admin.api.AdminController;
import run.halo.app.core.ControllerExceptionHandler;
import run.halo.app.model.dto.EnvironmentDTO;
import run.halo.app.model.dto.LoginPreCheckDTO;
import run.halo.app.model.entity.User;
import run.halo.app.model.params.LoginParam;
import run.halo.app.model.params.ResetPasswordParam;
import run.halo.app.model.params.ResetPasswordSendCodeParam;
import run.halo.app.security.token.AuthToken;
import run.halo.app.service.AdminService;
import run.halo.app.utils.JsonUtils;

/**
* Admin controller test.
*
* @author guqing
* @date 2022-02-11
*/
@ExtendWith(MockitoExtension.class)
public class AdminControllerTest {

private static final String FIELD_ERROR_MESSAGE = "字段验证错误,请完善后重试!";

private MockMvc mockMvc;

@BeforeEach
public void setUp() {
AdminController adminController =
new AdminController(new MockAdminService(), null);
mockMvc = MockMvcBuilders.standaloneSetup(adminController)
.setControllerAdvice(ControllerExceptionHandler.class)
.addFilter((request, response, chain) -> {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
chain.doFilter(request, response);
})
.build();
}

@Test
public void sendResetPasswordCodeShouldOk() throws Exception {
ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam();
param.setEmail("[email protected]");
param.setUsername("admin");
sendResetPasswordCodePerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().isOk());
}

@Test
public void sendResetPasswordCodeShould4xxError() throws Exception {
ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam();
param.setEmail("[email protected]");
sendResetPasswordCodePerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE));
}

@Test
public void sendResetPasswordCodeShould4xxErrorToo() throws Exception {
ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam();
param.setUsername("admin");
sendResetPasswordCodePerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE));
}

@Test
public void resetPasswordShouldOk() throws Exception {
ResetPasswordParam param = new ResetPasswordParam();
param.setUsername("admin");
param.setEmail("[email protected]");
param.setPassword("a password");
param.setCode("a code");
param.setPassword("12345678");
resetPasswordPerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().isOk());
}

@Test
public void resetPasswordAndEmailFieldAbsentShouldReturn4xxError() throws Exception {
ResetPasswordParam param = new ResetPasswordParam();
// The email field value in the parent class is missing,verification will also be triggered
param.setUsername("admin");
param.setCode("a code");
param.setPassword("a password");
resetPasswordPerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE));
}

@Test
public void resetPasswordAndCodeFieldAbsentShouldReturn4xxError() throws Exception {
ResetPasswordParam param = new ResetPasswordParam();
// The code field value in the param class is missing,verification will also be triggered
param.setUsername("admin");
param.setEmail("[email protected]");
param.setPassword("a password");
resetPasswordPerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE));
}

@Test
public void resetPasswordAndInsufficientPasswordLengthShouldReturn4xxError() throws Exception {
ResetPasswordParam param = new ResetPasswordParam();
// The code field value in the param class is missing,verification will also be triggered
param.setUsername("admin");
param.setEmail("[email protected]");
param.setCode("a code");
param.setPassword("123");
resetPasswordPerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.message").value(FIELD_ERROR_MESSAGE));
}

@NotNull
private ResultActions resetPasswordPerform(String param)
throws Exception {
return this.mockMvc.perform(put("/api/admin/password/reset")
.content(param)
.contentType(MediaType.APPLICATION_JSON));
}

@NotNull
private ResultActions sendResetPasswordCodePerform(String param)
throws Exception {
return this.mockMvc.perform(post("/api/admin/password/code")
.content(param)
.contentType(MediaType.APPLICATION_JSON));
}

public static class MockAdminService implements AdminService {

@Override
public User authenticate(LoginParam loginParam) {
return null;
}

@Override
public AuthToken authCodeCheck(LoginParam loginParam) {
return null;
}

@Override
public void clearToken() {

}

@Override
public void sendResetPasswordCode(ResetPasswordSendCodeParam param) {

}

@Override
public void resetPasswordByCode(ResetPasswordParam param) {

}

@Override
public EnvironmentDTO getEnvironments() {
return null;
}

@Override
public AuthToken refreshToken(String refreshToken) {
return null;
}

@Override
public String getLogFiles(Long lines) {
return null;
}

@Override
public LoginPreCheckDTO getUserEnv(String username) {
return null;
}
}
}

0 comments on commit 1ee7b58

Please sign in to comment.