first commit
This commit is contained in:
@@ -5,9 +5,13 @@ import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||
import com.miuna.subman.convert.UserConv;
|
||||
import com.miuna.subman.entity.Resp;
|
||||
import com.miuna.subman.entity.user.dto.AccessClaims;
|
||||
import com.miuna.subman.entity.user.dto.RefreshClaims;
|
||||
import com.miuna.subman.entity.user.enums.UserStatusEnum;
|
||||
import com.miuna.subman.entity.user.pojo.User;
|
||||
import com.miuna.subman.exception.AccountInactivatedException;
|
||||
import com.miuna.subman.exception.BasicException;
|
||||
import com.miuna.subman.exception.UnauthorizedException;
|
||||
import com.miuna.subman.service.UserServ;
|
||||
import com.miuna.subman.util.JsonUtil;
|
||||
@@ -19,6 +23,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -29,6 +35,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@@ -55,19 +62,12 @@ public class JwtFilter extends OncePerRequestFilter {
|
||||
return;
|
||||
}
|
||||
String newAccessToken = renewAccessToken(authorization.accessToken());
|
||||
|
||||
String newRefreshToken;
|
||||
if (validToken(authorization.refreshToken())) {
|
||||
DecodedJWT decodedJWT = JwtUtil.parseValidToken(authorization.refreshToken());
|
||||
RefreshClaims claims = RefreshClaims.fromClaimMap(decodedJWT.getClaims());
|
||||
setSecurityContext(claims.email(), claims.getAuthorities());
|
||||
newRefreshToken = "";
|
||||
} else {
|
||||
DecodedJWT decodedJWT = JwtUtil.parseValidToken(authorization.accessToken());
|
||||
AccessClaims claims = AccessClaims.fromClaimMap(decodedJWT.getClaims());
|
||||
User user = getValidUserByEmail(claims.email());
|
||||
setSecurityContext(user.getEmail(), user.getAuthorities());
|
||||
newRefreshToken = JwtUtil.generateRefreshToken(userConv.convUserToRefreshClaims(user));
|
||||
try {
|
||||
newRefreshToken = renewRefreshToken(authorization.refreshToken(), authorization.accessToken());
|
||||
} catch (BasicException e) {
|
||||
writeException2Resp(e, response);
|
||||
return;
|
||||
}
|
||||
Authorization newToken = new Authorization(newAccessToken, newRefreshToken);
|
||||
writeNewToken2Resp(newToken, response);
|
||||
@@ -83,16 +83,24 @@ public class JwtFilter extends OncePerRequestFilter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeException2Resp(BasicException e, HttpServletResponse response) throws IOException {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.getOutputStream().write(JsonUtil.toJson(Resp.failure(e)).getBytes());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
"{token}": the renewed token
|
||||
"": don't need renew
|
||||
*/
|
||||
private String renewAccessToken(String accessToken) throws JWTVerificationException {
|
||||
private String renewAccessToken(String accessToken) {
|
||||
DecodedJWT decodedJwt = JwtUtil.parseValidToken(accessToken);
|
||||
AccessClaims accessClaims = AccessClaims.fromClaimMap(decodedJwt.getClaims());
|
||||
setSecurityContext(accessClaims.email(), List.of());
|
||||
Instant expiresAt = decodedJwt.getExpiresAtAsInstant();
|
||||
if (JwtUtil.needRenew(expiresAt, ACCESS_TOKEN_RENEW_TIME)) {
|
||||
return JwtUtil.generateAccessToken(AccessClaims.fromClaimMap(decodedJwt.getClaims()));
|
||||
return JwtUtil.generateAccessToken(accessClaims);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -102,9 +110,27 @@ public class JwtFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private User getValidUserByEmail(String email) throws UnauthorizedException {
|
||||
return userServ.findUserByEmail(email)
|
||||
.filter(v -> !v.isEnabled())
|
||||
User user = userServ.findUserByEmail(email)
|
||||
.filter(User::isEnabled)
|
||||
.orElseThrow(UnauthorizedException::new);
|
||||
if (UserStatusEnum.INACTIVATED.getStatus().equals(user.getStatus())) {
|
||||
throw new AccountInactivatedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private String renewRefreshToken(String refreshToken, String accessToken) throws UnauthorizedException, AccountInactivatedException {
|
||||
if (validToken(refreshToken)) {
|
||||
DecodedJWT decodedJWT = JwtUtil.parseValidToken(refreshToken);
|
||||
RefreshClaims claims = RefreshClaims.fromClaimMap(decodedJWT.getClaims());
|
||||
setSecurityContext(claims.email(), claims.getAuthorities());
|
||||
return "";
|
||||
}
|
||||
DecodedJWT decodedJWT = JwtUtil.parseValidToken(accessToken);
|
||||
AccessClaims claims = AccessClaims.fromClaimMap(decodedJWT.getClaims());
|
||||
User user = getValidUserByEmail(claims.email());
|
||||
setSecurityContext(user.getEmail(), user.getAuthorities());
|
||||
return JwtUtil.generateRefreshToken(userConv.convUserToRefreshClaims(user));
|
||||
}
|
||||
|
||||
private void setSecurityContext(String email, Collection<? extends GrantedAuthority> authority) {
|
||||
|
||||
@@ -1,34 +1,98 @@
|
||||
package com.miuna.subman.controller;
|
||||
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import com.miuna.subman.entity.Resp;
|
||||
import com.miuna.subman.entity.user.pojo.User;
|
||||
import com.miuna.subman.entity.user.req.LoginReq;
|
||||
import com.miuna.subman.entity.user.req.RegisterReq;
|
||||
import com.miuna.subman.entity.user.req.*;
|
||||
import com.miuna.subman.entity.user.resp.GetUserResp;
|
||||
import com.miuna.subman.service.UserServ;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserCont {
|
||||
private final UserServ userServ;
|
||||
|
||||
@GetMapping("/findOne")
|
||||
@PreAuthorize("hasRole('SYSADMIN')")
|
||||
public User findOne() {
|
||||
return userServ.findUserByEmail("1853633282@qq.com").orElse(null);
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public Resp<Boolean> register(@RequestBody RegisterReq req) {
|
||||
userServ.register(req);
|
||||
return Resp.success(null);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public Resp<String> login(@RequestBody LoginReq req) {
|
||||
return Resp.success(userServ.login(req));
|
||||
}
|
||||
|
||||
@PutMapping("/forgetPwd")
|
||||
public Resp<Object> forgetPwd(@RequestBody @Valid ForgetPwdReq req) {
|
||||
userServ.forgetPwd(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/addUser")
|
||||
private Resp<Object> addUser(@RequestBody @Valid AddUserReq req) {
|
||||
userServ.addUser(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/updateUser")
|
||||
public Resp<Object> updateUser(@RequestBody @Valid UpdateUserReq req) {
|
||||
userServ.updateUser(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/resetPwd/{email}")
|
||||
public Resp<Object> resetPwd(@PathVariable @Email String email) {
|
||||
userServ.resetPwd(email);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/activate")
|
||||
public Resp<Object> activate(@RequestBody @Valid ActivateReq req) {
|
||||
req.setEmail(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
userServ.activate(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/changeUser")
|
||||
public Resp<Object> changeUser(@RequestBody @Valid ChangeUserReq req) {
|
||||
req.setEmail(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
userServ.changeUserInfo(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/changeEmail")
|
||||
public Resp<Object> changeEmail(@RequestBody @Valid ChangeEmailReq req) {
|
||||
req.setEmail(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
userServ.changeEmail(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@PutMapping("/changeUuid")
|
||||
public Resp<String> changeUuid(@RequestBody @Valid AuthenticatedReq req) {
|
||||
req.setEmail(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
return Resp.success(userServ.changeUuid(req));
|
||||
}
|
||||
|
||||
@PutMapping("/changePwd")
|
||||
public Resp<Object> changePwd(@RequestBody @Valid ChangePwdReq req) {
|
||||
req.setEmail(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
userServ.changePwd(req);
|
||||
return Resp.success();
|
||||
}
|
||||
|
||||
@GetMapping("/listAll")
|
||||
public Resp<List<GetUserResp>> listAllUser() {
|
||||
return Resp.success(userServ.listAllUser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,16 @@ import com.miuna.subman.entity.user.dto.AccessClaims;
|
||||
import com.miuna.subman.entity.user.dto.RefreshClaims;
|
||||
import com.miuna.subman.entity.user.pojo.User;
|
||||
import com.miuna.subman.entity.user.req.AddUserReq;
|
||||
import com.miuna.subman.entity.user.req.ChangeUserReq;
|
||||
import com.miuna.subman.entity.user.req.RegisterReq;
|
||||
import com.miuna.subman.entity.user.req.UpdateUserReq;
|
||||
import com.miuna.subman.entity.user.resp.GetUserResp;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface UserConv {
|
||||
@@ -15,18 +22,34 @@ public interface UserConv {
|
||||
RefreshClaims convUserToRefreshClaims(User user);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "timestamp", ignore = true)
|
||||
@Mapping(target = "deleted", constant = "false")
|
||||
@Mapping(target = "uuid", expression = "java(com.miuna.subman.util.CodeUtil.generateUUID())")
|
||||
@Mapping(target = "password", expression = "java(com.miuna.subman.util.PwdUtil.encode(req.getPassword()))")
|
||||
@Mapping(target = "role", expression = "java(com.miuna.subman.entity.user.enums.RoleEnum.USER.getRoleId())")
|
||||
@Mapping(target = "status", expression = "java(com.miuna.subman.entity.user.enums.UserStatusEnum.INACTIVATED.getStatus())")
|
||||
@Mapping(target = "status", expression = "java(com.miuna.subman.entity.user.enums.UserStatusEnum.ACTIVATED.getStatus())")
|
||||
User convRegister2User(RegisterReq req);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "timestamp", ignore = true)
|
||||
@Mapping(target = "deleted", constant = "false")
|
||||
@Mapping(target = "password", expression = "java(com.miuna.subman.util.PwdUtil.encodeDefault())")
|
||||
@Mapping(target = "uuid", expression = "java(com.miuna.subman.util.CodeUtil.generateUUID())")
|
||||
@Mapping(target = "status", expression = "java(com.miuna.subman.entity.user.enums.UserStatusEnum.INACTIVATED.getStatus())")
|
||||
User convAddUser2User(AddUserReq req);
|
||||
|
||||
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "role")
|
||||
@Mapping(target = "email")
|
||||
@Mapping(target = "status")
|
||||
@Mapping(target = "username")
|
||||
void fillUserWithUpdateUser(@MappingTarget User user, UpdateUserReq req);
|
||||
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "username")
|
||||
void fillUserWithChangeUser(@MappingTarget User user, ChangeUserReq req);
|
||||
|
||||
List<GetUserResp> convUser2GetUsers(List<User> users);
|
||||
|
||||
}
|
||||
|
||||
10
src/main/java/com/miuna/subman/entity/AuthenticatedReq.java
Normal file
10
src/main/java/com/miuna/subman/entity/AuthenticatedReq.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.miuna.subman.entity;
|
||||
|
||||
import jakarta.validation.constraints.Null;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthenticatedReq {
|
||||
@Null
|
||||
private String email;
|
||||
}
|
||||
@@ -18,5 +18,5 @@ public class Code {
|
||||
private String code;
|
||||
|
||||
@UpdateTimestamp
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime timestamp;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,13 @@ package com.miuna.subman.entity.user.pojo;
|
||||
|
||||
import com.miuna.subman.entity.user.enums.RoleEnum;
|
||||
import com.miuna.subman.entity.user.enums.UserStatusEnum;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
import org.hibernate.annotations.Where;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
@@ -26,6 +23,7 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Where(clause = "deleted = false")
|
||||
public class User implements UserDetails, Serializable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -41,13 +39,12 @@ public class User implements UserDetails, Serializable {
|
||||
|
||||
private Integer role;
|
||||
|
||||
private String uuid;
|
||||
|
||||
private Boolean deleted = Boolean.FALSE;
|
||||
|
||||
@CreationTimestamp
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@UpdateTimestamp
|
||||
private LocalDateTime updateTime;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
@@ -70,6 +67,6 @@ public class User implements UserDetails, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return UserStatusEnum.ACTIVATED.getStatus().equals(this.status) && !deleted;
|
||||
return !UserStatusEnum.DISABLE.getStatus().equals(this.status) && !deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ActivateReq extends AuthenticatedReq {
|
||||
@NotEmpty
|
||||
private String password;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -10,8 +12,9 @@ import lombok.experimental.Accessors;
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class AddUserReq {
|
||||
@NotEmpty
|
||||
private String username;
|
||||
@Email
|
||||
private String email;
|
||||
private String role;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChangeEmailReq extends AuthenticatedReq {
|
||||
@Email
|
||||
private String newEmail;
|
||||
@NotEmpty
|
||||
@Size(min = 6, max = 6)
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChangePwdReq extends AuthenticatedReq {
|
||||
@NotEmpty
|
||||
private String oldPassword;
|
||||
@NotEmpty
|
||||
private String newPassword;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChangeUserReq extends AuthenticatedReq {
|
||||
@NotEmpty
|
||||
private String username;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ForgetPwdReq {
|
||||
@Email
|
||||
private String email;
|
||||
@NotEmpty
|
||||
private String password;
|
||||
@NotEmpty
|
||||
@Size(min = 6, max = 6)
|
||||
private String code;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -10,6 +13,9 @@ import lombok.experimental.Accessors;
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class LoginReq {
|
||||
@Email
|
||||
private String email;
|
||||
@NotEmpty
|
||||
@Size(min = 6, max = 6)
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -10,8 +13,13 @@ import lombok.experimental.Accessors;
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class RegisterReq {
|
||||
@NotEmpty
|
||||
private String username;
|
||||
@NotEmpty
|
||||
private String password;
|
||||
@Email
|
||||
private String email;
|
||||
@NotEmpty
|
||||
@Size(min = 6, max = 6)
|
||||
private String code;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.miuna.subman.entity.user.req;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateUserReq {
|
||||
@Email
|
||||
private String email;
|
||||
@NotEmpty
|
||||
private String username;
|
||||
@NotNull
|
||||
private Integer role;
|
||||
@NotNull
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.miuna.subman.entity.user.resp;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class GetUserResp {
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String email;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Integer role;
|
||||
|
||||
private String uuid;
|
||||
|
||||
private Boolean deleted;
|
||||
|
||||
private LocalDateTime timestamp;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.miuna.subman.entity.user.resp;
|
||||
|
||||
public class LoginResp {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class AccountInactivatedException extends BasicException {
|
||||
public AccountInactivatedException() {
|
||||
super(ErrorEnum.ACCOUNT_INACTIVATED.getCode(), ErrorEnum.ACCOUNT_INACTIVATED.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class CodeIncorrectException extends BasicException{
|
||||
public CodeIncorrectException() {
|
||||
super(ErrorEnum.CODE_INCORRECT.getCode(), ErrorEnum.CODE_INCORRECT.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class CodeNotFoundException extends BasicException {
|
||||
public CodeNotFoundException() {
|
||||
super(ErrorEnum.CODE_NOT_FOUND.getCode(), ErrorEnum.CODE_NOT_FOUND.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class DuplicateEmailException extends BasicException{
|
||||
public DuplicateEmailException() {
|
||||
super(ErrorEnum.DUPLICATE_EMAIL.getCode(), ErrorEnum.DUPLICATE_EMAIL.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class EmailServiceException extends BasicException {
|
||||
public EmailServiceException() {
|
||||
super(ErrorEnum.EMAIL_SERVICE_UNAVAILABLE.getCode(), ErrorEnum.EMAIL_SERVICE_UNAVAILABLE.getDesc());
|
||||
}
|
||||
}
|
||||
21
src/main/java/com/miuna/subman/exception/ErrorEnum.java
Normal file
21
src/main/java/com/miuna/subman/exception/ErrorEnum.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ErrorEnum {
|
||||
|
||||
PASSWORD_INCORRECT(40001, "Password Incorrect"),
|
||||
CODE_INCORRECT(40002, "Code Incorrect"),
|
||||
DUPLICATE_EMAIL(40011, "Duplicate Email"),
|
||||
RATE_LIMIT_EXCEEDED(40301, "Rate Limit Exceeded"),
|
||||
USER_NOT_FOUND(40401, "User Not Found"),
|
||||
CODE_NOT_FOUND(40402, "Code Not Found"),
|
||||
ACCOUNT_INACTIVATED(40301, "Account Inactivated"),
|
||||
EMAIL_SERVICE_UNAVAILABLE(50011, "Email Service Unavailable");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class PasswordIncorrectException extends BasicException{
|
||||
public PasswordIncorrectException() {
|
||||
super(ErrorEnum.PASSWORD_INCORRECT.getCode(), ErrorEnum.PASSWORD_INCORRECT.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class RateLimitException extends BasicException{
|
||||
public RateLimitException() {
|
||||
super(ErrorEnum.RATE_LIMIT_EXCEEDED.getCode(), ErrorEnum.RATE_LIMIT_EXCEEDED.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
import com.miuna.subman.entity.Resp;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@ControllerAdvice
|
||||
public class SubManExceptionHandler {
|
||||
@ExceptionHandler(BasicException.class)
|
||||
public Resp<Object> handleBasicException(BasicException e) {
|
||||
return Resp.failure(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.miuna.subman.exception;
|
||||
|
||||
public class UserNotFountException extends BasicException {
|
||||
public UserNotFountException() {
|
||||
super(ErrorEnum.USER_NOT_FOUND.getCode(), ErrorEnum.USER_NOT_FOUND.getDesc());
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ import java.time.LocalDateTime;
|
||||
public interface CodeRepo extends JpaRepository<Code, String> {
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("delete from Code where createTime < :createTime")
|
||||
@Query("delete from Code where timestamp < :createTime")
|
||||
void deleteByCreateTimeBefore(LocalDateTime createTime);
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ import java.util.Optional;
|
||||
@Repository
|
||||
public interface UserRepo extends JpaRepository<User, Long> {
|
||||
Optional<User> findByEmail(@NonNull String email);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.miuna.subman.service;
|
||||
|
||||
import com.miuna.subman.entity.Constant;
|
||||
import com.miuna.subman.entity.user.pojo.Code;
|
||||
import com.miuna.subman.exception.CodeNotFoundException;
|
||||
import com.miuna.subman.exception.RateLimitException;
|
||||
import com.miuna.subman.repository.CodeRepo;
|
||||
import com.miuna.subman.util.CodeUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -19,14 +22,18 @@ public class CodeServ {
|
||||
private static final long RESEND_SECOND = 60;
|
||||
|
||||
public String generateCode(String email) {
|
||||
Code code = codeRepo.findById(email).filter(this::canResend).orElseThrow();
|
||||
codeRepo.save(new Code().setCode(CodeUtil.generateCode()).setEmail(email));
|
||||
Optional<Code> code = codeRepo.findById(email);
|
||||
if (!canResend(code)){
|
||||
throw new RateLimitException();
|
||||
}
|
||||
String newCode = CodeUtil.generateCode();
|
||||
codeRepo.save(new Code().setCode(newCode).setEmail(email));
|
||||
lazyDeleteCode();
|
||||
return code.getCode();
|
||||
return newCode;
|
||||
}
|
||||
|
||||
public String getCode(String email) {
|
||||
Code code = codeRepo.findById(email).filter(this::isNotExpired).orElseThrow();
|
||||
Code code = codeRepo.findById(email).filter(this::isNotExpired).orElseThrow(CodeNotFoundException::new);
|
||||
return code.getCode();
|
||||
}
|
||||
|
||||
@@ -43,11 +50,11 @@ public class CodeServ {
|
||||
}
|
||||
|
||||
private boolean isNotExpired(Code code) {
|
||||
return code.getCreateTime().isBefore(LocalDateTime.now().minusSeconds(Constant.VALID_CODE_EXPIRE_SECONDS));
|
||||
return code.getTimestamp().isAfter(LocalDateTime.now().minusSeconds(Constant.VALID_CODE_EXPIRE_SECONDS));
|
||||
}
|
||||
|
||||
private boolean canResend(Code code) {
|
||||
return code.getCreateTime().isAfter(LocalDateTime.now().minusSeconds(RESEND_SECOND));
|
||||
private boolean canResend(Optional<Code> code) {
|
||||
return code.isEmpty() || code.get().getTimestamp().isBefore(LocalDateTime.now().minusSeconds(RESEND_SECOND));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package com.miuna.subman.service;
|
||||
|
||||
import com.miuna.subman.entity.Constant;
|
||||
import com.miuna.subman.entity.system.pojo.System;
|
||||
import com.miuna.subman.exception.EmailServiceException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
// TODO: valid email service switch
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class EmailServ {
|
||||
@@ -32,7 +34,7 @@ public class EmailServ {
|
||||
try {
|
||||
sender.send(smm);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException();
|
||||
throw new EmailServiceException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package com.miuna.subman.service;
|
||||
|
||||
import com.miuna.subman.convert.UserConv;
|
||||
import com.miuna.subman.entity.AuthenticatedReq;
|
||||
import com.miuna.subman.entity.user.enums.UserStatusEnum;
|
||||
import com.miuna.subman.entity.user.pojo.User;
|
||||
import com.miuna.subman.entity.user.req.AddUserReq;
|
||||
import com.miuna.subman.entity.user.req.LoginReq;
|
||||
import com.miuna.subman.entity.user.req.RegisterReq;
|
||||
import com.miuna.subman.entity.user.req.*;
|
||||
import com.miuna.subman.entity.user.resp.GetUserResp;
|
||||
import com.miuna.subman.exception.CodeIncorrectException;
|
||||
import com.miuna.subman.exception.DuplicateEmailException;
|
||||
import com.miuna.subman.exception.PasswordIncorrectException;
|
||||
import com.miuna.subman.exception.UserNotFountException;
|
||||
import com.miuna.subman.repository.UserRepo;
|
||||
import com.miuna.subman.util.CodeUtil;
|
||||
import com.miuna.subman.util.JwtUtil;
|
||||
import com.miuna.subman.util.PwdUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -25,24 +31,22 @@ public class UserServ {
|
||||
private final UserConv userConv;
|
||||
private final CodeServ codeServ;
|
||||
|
||||
private static final PasswordEncoder PWD_ENCODER = new BCryptPasswordEncoder();
|
||||
|
||||
public Optional<User> findUserByEmail(String email) {
|
||||
return userRepo.findByEmail(email);
|
||||
}
|
||||
|
||||
public void register(RegisterReq req) {
|
||||
// TODO: register switch open
|
||||
validEmail(req.getEmail());
|
||||
validCode(req.getEmail(), req.getCode());
|
||||
User user = userConv.convRegister2User(req);
|
||||
user.setPassword(PWD_ENCODER.encode(req.getPassword()));
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public String login(LoginReq req) {
|
||||
User user = findUserByEmail(req.getEmail()).orElseThrow(RuntimeException::new);
|
||||
if (!PWD_ENCODER.matches(req.getPassword(), user.getPassword())) {
|
||||
throw new RuntimeException();
|
||||
User user = findUserByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
if (!PwdUtil.match(req.getPassword(), user.getPassword())) {
|
||||
throw new PasswordIncorrectException();
|
||||
}
|
||||
return JwtUtil.generateAccessToken(userConv.convUser2AccessClaims(user));
|
||||
}
|
||||
@@ -50,33 +54,78 @@ public class UserServ {
|
||||
public void addUser(AddUserReq req) {
|
||||
validEmail(req.getEmail());
|
||||
User user = userConv.convAddUser2User(req);
|
||||
user.setPassword(PWD_ENCODER.encode(user.getPassword()));
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void updateUser() {}
|
||||
public void forgetPwd(ForgetPwdReq req) {
|
||||
validCode(req.getEmail(), req.getCode());
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
user.setPassword(PwdUtil.encode(req.getPassword()));
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void resetPassword() {}
|
||||
public void resetPwd(String email) {
|
||||
User user = userRepo.findByEmail(email).orElseThrow(UserNotFountException::new);
|
||||
user.setPassword(PwdUtil.encodeDefault()).setStatus(UserStatusEnum.INACTIVATED.ordinal());
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void changeUserInfo() {}
|
||||
public void activate(ActivateReq req) {
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
user.setPassword(PwdUtil.encode(req.getPassword())).setStatus(UserStatusEnum.ACTIVATED.getStatus());
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void changeEmail() {}
|
||||
public void updateUser(UpdateUserReq req) {
|
||||
validEmail(req.getEmail());
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
userConv.fillUserWithUpdateUser(user, req);
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void changePassword(){}
|
||||
public void changePwd(ChangePwdReq req) {
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
if (!PwdUtil.match(req.getOldPassword(), user.getPassword())) {
|
||||
throw new PasswordIncorrectException();
|
||||
}
|
||||
user.setPassword(PwdUtil.encode(req.getNewPassword()));
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void listAllUser() {}
|
||||
public void changeUserInfo(ChangeUserReq req) {
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
userConv.fillUserWithChangeUser(user, req);
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public void changeEmail(ChangeEmailReq req) {
|
||||
validCode(req.getNewEmail(), req.getCode());
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
user.setEmail(req.getEmail());
|
||||
userRepo.save(user);
|
||||
}
|
||||
|
||||
public String changeUuid(AuthenticatedReq req) {
|
||||
User user = userRepo.findByEmail(req.getEmail()).orElseThrow(UserNotFountException::new);
|
||||
String uuid = CodeUtil.generateUUID();
|
||||
user.setUuid(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public List<GetUserResp> listAllUser() {
|
||||
return userConv.convUser2GetUsers(userRepo.findAll());
|
||||
}
|
||||
|
||||
private void validCode(String email, String code) {
|
||||
if (!Objects.equals(code, codeServ.getCode(email))) {
|
||||
throw new RuntimeException();
|
||||
throw new CodeIncorrectException();
|
||||
}
|
||||
codeServ.deleteCode(email);
|
||||
}
|
||||
|
||||
private void validEmail(String email) {
|
||||
boolean isExist = userRepo.findByEmail(email).filter(v -> !v.getDeleted()).isPresent();
|
||||
if (isExist) {
|
||||
throw new RuntimeException();
|
||||
if (!userRepo.existsByEmail(email)) {
|
||||
throw new DuplicateEmailException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.miuna.subman.util;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CodeUtil {
|
||||
@@ -8,4 +9,8 @@ public class CodeUtil {
|
||||
return new Random().ints(6, 0, 10)
|
||||
.mapToObj(String::valueOf).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
public static String generateUUID() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
|
||||
22
src/main/java/com/miuna/subman/util/PwdUtil.java
Normal file
22
src/main/java/com/miuna/subman/util/PwdUtil.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.miuna.subman.util;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
public class PwdUtil {
|
||||
private static final PasswordEncoder PWD_ENCODER = new BCryptPasswordEncoder();
|
||||
private static final String DEFAULT_PWD = "SubMan123";
|
||||
|
||||
public static String encode(String raw) {
|
||||
return PWD_ENCODER.encode(raw);
|
||||
}
|
||||
|
||||
public static String encodeDefault() {
|
||||
return PWD_ENCODER.encode(DEFAULT_PWD);
|
||||
}
|
||||
|
||||
public static boolean match(String raw, String encode) {
|
||||
return PWD_ENCODER.matches(raw, encode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ spring:
|
||||
name: subman
|
||||
devtools:
|
||||
restart:
|
||||
enabled: true
|
||||
enabled: false
|
||||
datasource:
|
||||
url: jdbc:sqlite:${user.home}/.config/subman/subman.db
|
||||
jpa:
|
||||
|
||||
Reference in New Issue
Block a user