Add password reset field verification (#1636)

* perf: add password reset field verification

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: add unit test case

Co-authored-by: guqing <1484563614@qq.com>
pull/1666/head
Ryan Wang 2022-02-20 10:30:42 +08:00 committed by GitHub
parent acf6a98d25
commit 1ee7b58ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 236 additions and 10 deletions

View File

@ -20,6 +20,7 @@ import run.halo.app.model.entity.User;
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;
@ -80,7 +81,7 @@ public class AdminController {
@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);
}

View File

@ -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.
@ -10,15 +12,13 @@ import lombok.Data;
* @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;
}

View File

@ -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;
}

View File

@ -6,6 +6,7 @@ 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;
/**
@ -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.

View File

@ -32,6 +32,7 @@ import run.halo.app.model.enums.LogType;
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;
@ -184,7 +185,7 @@ public class AdminServiceImpl implements AdminService {
}
@Override
public void sendResetPasswordCode(ResetPasswordParam param) {
public void sendResetPasswordCode(ResetPasswordSendCodeParam param) {
cacheStore.getAny("code", String.class).ifPresent(code -> {
throw new ServiceException("已经获取过验证码,不能重复获取");
});

View File

@ -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("example@example.com");
param.setUsername("admin");
sendResetPasswordCodePerform(JsonUtils.objectToJson(param))
.andDo(print())
.andExpect(status().isOk());
}
@Test
public void sendResetPasswordCodeShould4xxError() throws Exception {
ResetPasswordSendCodeParam param = new ResetPasswordSendCodeParam();
param.setEmail("example@example.com");
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("example@example.com");
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 missingverification 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 missingverification will also be triggered
param.setUsername("admin");
param.setEmail("example@example.com");
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 missingverification will also be triggered
param.setUsername("admin");
param.setEmail("example@example.com");
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;
}
}
}