Authorization working
This commit is contained in:
17
src/main/java/com/mattrixwv/raidbuilder/RaidBuilderAPI.java
Normal file
17
src/main/java/com/mattrixwv/raidbuilder/RaidBuilderAPI.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.mattrixwv.raidbuilder;
|
||||
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
import com.mattrixwv.raidbuilder.config.RsaKeyProperties;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(RsaKeyProperties.class)
|
||||
public class RaidBuilderAPI{
|
||||
public static void main(String[] args){
|
||||
SpringApplication.run(RaidBuilderAPI.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.mattrixwv.raidbuilder.annotation;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
|
||||
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AccountAuthorization{
|
||||
public AccountPermissionType[] permissions();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.mattrixwv.raidbuilder.aspect;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import com.mattrixwv.raidbuilder.annotation.AccountAuthorization;
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
import com.mattrixwv.raidbuilder.entity.AccountPermission;
|
||||
import com.mattrixwv.raidbuilder.exception.MissingAuthorizationException;
|
||||
import com.mattrixwv.raidbuilder.service.AccountPermissionService;
|
||||
import com.mattrixwv.raidbuilder.service.AccountService;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class AccountAuthorizationAspect{
|
||||
private final AccountService accountService;
|
||||
private final AccountPermissionService accountPermissionService;
|
||||
|
||||
|
||||
@Pointcut("@annotation(com.mattrixwv.raidbuilder.annotation.AccountAuthorization)")
|
||||
public void accountAuthorizationAnnotation(){
|
||||
//Intentionally blank
|
||||
}
|
||||
|
||||
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
|
||||
public void mappedFunction(){
|
||||
//Intentionally blank
|
||||
}
|
||||
|
||||
|
||||
@Before("accountAuthorizationAnnotation()")
|
||||
public void authorizeAccount(JoinPoint joinPoint){
|
||||
log.debug("Authorizing account");
|
||||
|
||||
|
||||
//Get the annotation
|
||||
AccountAuthorization accountAuthorization = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(AccountAuthorization.class);
|
||||
log.debug("Required authorizations = {}", accountAuthorization);
|
||||
//Return if there are no required permissions
|
||||
if(accountAuthorization.permissions().length == 0){
|
||||
log.debug("No required permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
//Get the account
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
String username = ((Jwt)auth.getPrincipal()).getClaimAsString("sub");
|
||||
Account account = accountService.getByUsername(username);
|
||||
if(account.getAccountStatus() != AccountStatus.ACTIVE){
|
||||
throw new AuthorizationDeniedException("Account is not active", () -> false);
|
||||
}
|
||||
List<AccountPermission> accountPermissions = accountPermissionService.getByAccountId(account.getAccountId());
|
||||
|
||||
//Return if the account has a matching permissions
|
||||
for(AccountPermission permission : accountPermissions){
|
||||
for(AccountPermissionType permissionType : accountAuthorization.permissions()){
|
||||
if(permission.getAccountPermissionType() == permissionType){
|
||||
log.debug("User is authorized");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("User is not authorized");
|
||||
|
||||
//If the user doesn't have a matching permission, throw an authorization exception
|
||||
throw new AuthorizationDeniedException("User is not authorized to perform this action", () -> false);
|
||||
}
|
||||
|
||||
|
||||
@Before("mappedFunction() && !accountAuthorizationAnnotation()")
|
||||
public void missingAuthorization(){
|
||||
throw new MissingAuthorizationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.mattrixwv.raidbuilder.config;
|
||||
|
||||
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = "rsa")
|
||||
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey){
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.mattrixwv.raidbuilder.config;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig{
|
||||
private final RsaKeyProperties rsaKeys;
|
||||
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder(){
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> {
|
||||
auth.requestMatchers("/auth/refresh").permitAll() //Permit refresh tokens
|
||||
.requestMatchers(HttpMethod.POST, "/auth/signup", "/auth/confirm").permitAll() //Permit signup operations
|
||||
.requestMatchers("/auth/forgot", "/auth/forgot/*").permitAll() //Permit forgot password operations
|
||||
.anyRequest().authenticated();
|
||||
})
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public JwtEncoder jwtEncoder(){
|
||||
JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build();
|
||||
|
||||
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
|
||||
|
||||
return new NimbusJwtEncoder(jwks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(){
|
||||
return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.mattrixwv.raidbuilder.config;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
import com.mattrixwv.raidbuilder.service.AccountPermissionService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class TokenService{
|
||||
private final JwtEncoder encoder;
|
||||
private final AccountPermissionService accountPermissionService;
|
||||
//Fields
|
||||
@Value("${jwt.accessTokenDuration}")
|
||||
private Duration accessTokenDuration;
|
||||
|
||||
|
||||
public String generateAccessToken(Account account){
|
||||
log.debug("Generating access token for account {}", account.getAccountId());
|
||||
|
||||
|
||||
String scope = accountPermissionService.getByAccountId(account.getAccountId()).stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "));
|
||||
|
||||
Instant now = Instant.now();
|
||||
JwtClaimsSet claims = JwtClaimsSet.builder()
|
||||
.issuer("self")
|
||||
.issuedAt(now)
|
||||
.expiresAt(now.plus(accessTokenDuration))
|
||||
.subject(account.getUsername())
|
||||
.claim("scope", scope)
|
||||
.build();
|
||||
|
||||
return encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.mattrixwv.raidbuilder.config;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module;
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module.Feature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer{
|
||||
@Value("${allowedOrigins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(@NonNull CorsRegistry registry){
|
||||
log.debug("Adding CORS mappings: {}", allowedOrigins);
|
||||
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns(allowedOrigins)
|
||||
.allowedMethods("GET", "PUT", "DELETE", "OPTIONS", "PATCH", "POST");
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper(){
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
log.debug("Starting mapping configuration");
|
||||
|
||||
//Make sure Jackson doesn't attempt lazy loading
|
||||
Hibernate6Module hibernate6Module = new Hibernate6Module();
|
||||
hibernate6Module.configure(Feature.FORCE_LAZY_LOADING, false);
|
||||
hibernate6Module.configure(Feature.USE_TRANSIENT_ANNOTATION, false);
|
||||
hibernate6Module.configure(Feature.REQUIRE_EXPLICIT_LAZY_LOADING_MARKER, true);
|
||||
hibernate6Module.configure(Feature.WRITE_MISSING_ENTITIES_AS_NULL, false);
|
||||
mapper.registerModule(hibernate6Module);
|
||||
|
||||
//Print dates as strings
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
mapper.findAndRegisterModules();
|
||||
|
||||
log.debug("Completed mapping configuration");
|
||||
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.mattrixwv.raidbuilder.config;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WebFilter extends OncePerRequestFilter{
|
||||
@Override
|
||||
public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException{
|
||||
if(!request.getMethod().equalsIgnoreCase("OPTIONS")){
|
||||
setupMDC(request);
|
||||
}
|
||||
|
||||
//Continue to the controller
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
//Clear the MDC for the next request
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
private void setupMDC(HttpServletRequest request){
|
||||
//Get the requestId
|
||||
String requestId = request.getHeader("X-Request-Id");
|
||||
if(requestId != null){
|
||||
MDC.put("requestId", requestId);
|
||||
}
|
||||
|
||||
//Get IP address
|
||||
String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
if(forwardedFor != null){
|
||||
MDC.put("ip", forwardedFor.split(",")[0]);
|
||||
}
|
||||
|
||||
//Get all of the parameters in the request and print them in the log
|
||||
StringJoiner parameters = new StringJoiner(", ");
|
||||
request.getParameterMap().entrySet().forEach(entry -> {
|
||||
if(!entry.getKey().equals("_")){
|
||||
String key = entry.getKey();
|
||||
String value = "";
|
||||
if(entry.getValue().length > 1){
|
||||
StringJoiner joiner = new StringJoiner(", ", "[", "]");
|
||||
for(String str : entry.getValue()){
|
||||
joiner.add(str);
|
||||
}
|
||||
value = joiner.toString();
|
||||
}
|
||||
else{
|
||||
value = entry.getValue()[0];
|
||||
}
|
||||
parameters.add(key + "->" + value);
|
||||
}
|
||||
});
|
||||
if(parameters.length() > 0){
|
||||
log.info("Request parameters: {}", parameters);
|
||||
}
|
||||
|
||||
//Get the path
|
||||
MDC.put("url", request.getRequestURI());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package com.mattrixwv.raidbuilder.controller;
|
||||
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.mattrixwv.raidbuilder.annotation.AccountAuthorization;
|
||||
import com.mattrixwv.raidbuilder.config.TokenService;
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
import com.mattrixwv.raidbuilder.service.AccountService;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationController{
|
||||
private final ObjectMapper mapper;
|
||||
private final TokenService tokenService;
|
||||
private final AccountService accountService;
|
||||
|
||||
@PostMapping("/token")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode token(Authentication authentication){
|
||||
log.info("Token requested for user {}", authentication.getName());
|
||||
|
||||
|
||||
Account account = accountService.getByUsername(authentication.getName());
|
||||
String token = tokenService.generateAccessToken(account);
|
||||
log.debug("Token granted {}", token);
|
||||
ObjectNode tokenNode = mapper.valueToTree(account);
|
||||
tokenNode.put("token", token);
|
||||
|
||||
|
||||
return tokenNode;
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode refresh(@RequestBody ObjectNode refreshTokenNode){
|
||||
log.info("Refreshing token");
|
||||
|
||||
|
||||
UUID refreshToken = UUID.fromString(refreshTokenNode.get("refreshToken").asText());
|
||||
log.debug("refreshToken: {}", refreshToken);
|
||||
|
||||
Account account = accountService.getByRefreshToken(refreshToken);
|
||||
|
||||
if(account == null){
|
||||
throw new AuthorizationDeniedException("Refresh token is invalid", () -> false);
|
||||
}
|
||||
|
||||
//Update login date
|
||||
account.setLoginDate(ZonedDateTime.now());
|
||||
account = accountService.updateAccount(account);
|
||||
|
||||
String token = tokenService.generateAccessToken(account);
|
||||
log.debug("new token: {}", token);
|
||||
ObjectNode tokenNode = mapper.valueToTree(account);
|
||||
tokenNode.put("token", token);
|
||||
|
||||
return tokenNode;
|
||||
}
|
||||
|
||||
@PostMapping("/signup")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode signup(@RequestBody Account account){
|
||||
log.info("Creating account {}", account.getUsername());
|
||||
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
|
||||
//Verify account object
|
||||
List<String> errors = verifyNewAccount(account);
|
||||
if(errors.isEmpty()){
|
||||
//Create the account
|
||||
account = accountService.createAccount(account);
|
||||
|
||||
returnNode.put("accountId", account.getAccountId().toString());
|
||||
returnNode.put("status", "success");
|
||||
|
||||
//TODO: Send email
|
||||
|
||||
log.info("Successfully created account: {}", account.getAccountId());
|
||||
}
|
||||
else{
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errors.forEach(errorsNode::add);
|
||||
returnNode.set("errors", errorsNode);
|
||||
|
||||
log.info("Error creating account: {}", errors);
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PostMapping("/confirm")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode confirm(@RequestBody ObjectNode confirmNode){
|
||||
UUID confirmToken = UUID.fromString(confirmNode.get("confirmToken").asText());
|
||||
log.info("Confirming account with token {}", confirmToken);
|
||||
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
|
||||
//Verify token
|
||||
Account account = accountService.getByRefreshToken(confirmToken);
|
||||
log.debug("Found account: {}", account);
|
||||
if((account != null) && (account.getAccountStatus() == AccountStatus.UNCONFIRMED) && (account.getRefreshTokenExpiration().isAfter(ZonedDateTime.now()))){
|
||||
accountService.confirmAccount(account);
|
||||
|
||||
returnNode.put("status", "success");
|
||||
}
|
||||
else{
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add("Account is not unconfirmed");
|
||||
returnNode.set("errors", errorsNode);
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PostMapping("/forgot")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode forgot(@RequestParam("loginId") String loginId){
|
||||
log.info("Setting up user that forgot their password: {}", loginId);
|
||||
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
|
||||
//Verify the account exists
|
||||
Account account = accountService.getByUsername(loginId);
|
||||
if(account != null){
|
||||
//Setup token
|
||||
UUID token = UUID.randomUUID();
|
||||
account.setRefreshToken(token);
|
||||
account.setRefreshTokenExpiration(ZonedDateTime.now().plusHours(1));
|
||||
account.setAccountStatus(AccountStatus.LOCKED);
|
||||
|
||||
account = accountService.updateAccount(account);
|
||||
|
||||
//TODO: Send email
|
||||
|
||||
returnNode.put("status", "success");
|
||||
}
|
||||
else{
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add("Could not find account with login " + loginId);
|
||||
returnNode.set("errors", errorsNode);
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PostMapping("/forgot/reset")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode setNewPasswordForgot(@RequestBody ObjectNode forgotNode){
|
||||
UUID forgotToken = UUID.fromString(forgotNode.get("forgotToken").asText());
|
||||
String newPassword = forgotNode.get("password").asText();
|
||||
log.info("Confirming user reset password (forget) with token {}", forgotToken);
|
||||
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
|
||||
|
||||
//Verify the account exists
|
||||
Account existingAccount = accountService.getByRefreshToken(forgotToken);
|
||||
if(existingAccount != null){
|
||||
existingAccount = accountService.updatePassword(existingAccount.getAccountId(), newPassword);
|
||||
|
||||
existingAccount.setRefreshToken(null);
|
||||
existingAccount.setRefreshTokenExpiration(null);
|
||||
existingAccount.setAccountStatus(AccountStatus.ACTIVE);
|
||||
existingAccount = accountService.updateAccount(existingAccount);
|
||||
|
||||
returnNode.put("status", "success");
|
||||
}
|
||||
else{
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add("Invalid token");
|
||||
returnNode.set("errors", errorsNode);
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PostMapping("/resetPassword")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode resetPassword(Authentication authentication, @RequestBody ObjectNode requestNode){
|
||||
log.info("Resetting password for {}", authentication.getName());
|
||||
|
||||
|
||||
if((requestNode == null) || (!requestNode.has("password"))){
|
||||
throw new IllegalArgumentException("Invalid request");
|
||||
}
|
||||
|
||||
Account account = accountService.getByUsername(authentication.getName());
|
||||
accountService.updatePassword(account.getAccountId(), requestNode.get("password").asText());
|
||||
account.setForceReset(false);
|
||||
accountService.updateAccount(account);
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
returnNode.put("status", "success");
|
||||
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@AccountAuthorization(permissions = {})
|
||||
public ObjectNode logout(Authentication authentication){
|
||||
log.info("Logging out account {}", authentication.getName());
|
||||
|
||||
|
||||
Account account = accountService.getByUsername(authentication.getName());
|
||||
if(account != null){
|
||||
account.setRefreshToken(null);
|
||||
account.setRefreshTokenExpiration(null);
|
||||
account = accountService.updateAccount(account);
|
||||
}
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
returnNode.put("status", "success");
|
||||
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@PutMapping("/{accountId}/revokeRefreshToken")
|
||||
@AccountAuthorization(permissions = {AccountPermissionType.ADMIN})
|
||||
public ObjectNode revokeRefreshToken(@PathVariable("accountId") UUID accountId){
|
||||
log.info("Revoking refresh token for account {}", accountId);
|
||||
|
||||
|
||||
Account account = accountService.getByAccountId(accountId);
|
||||
account.setRefreshToken(null);
|
||||
account.setRefreshTokenExpiration(null);
|
||||
account = accountService.updateAccount(account);
|
||||
|
||||
ObjectNode returnNode = mapper.createObjectNode();
|
||||
returnNode.put("status", "success");
|
||||
returnNode.put("accountId", account.getAccountId().toString());
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
|
||||
private List<String> verifyNewAccount(Account account){
|
||||
ArrayList<String> errors = new ArrayList<>();
|
||||
|
||||
//Check ID
|
||||
if(account.getAccountId() != null){
|
||||
errors.add("Invalid Account ID");
|
||||
}
|
||||
|
||||
//Check login exists in entity
|
||||
if((account.getUsername() == null) || (account.getUsername().isEmpty())){
|
||||
errors.add("Invalid Login ID");
|
||||
}
|
||||
else{
|
||||
//Check login doesn't exist in db
|
||||
Account existingAccount = accountService.getByUsername(account.getUsername());
|
||||
if(existingAccount != null){
|
||||
errors.add("Login ID already exists");
|
||||
}
|
||||
}
|
||||
|
||||
//Check password exists in entity
|
||||
if((account.getPassword() == null) || (account.getPassword().isEmpty())){
|
||||
errors.add("No password found");
|
||||
}
|
||||
|
||||
//Check email exists in entity
|
||||
if((account.getEmail() == null) || (account.getEmail().isEmpty())){
|
||||
errors.add("Invalid email");
|
||||
}
|
||||
else{
|
||||
//Check email doesn't exist in db
|
||||
Account existingAccount = accountService.getByEmail(account.getEmail());
|
||||
if(existingAccount != null){
|
||||
errors.add("Account with email already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private List<String> verifyUpdatedAccount(Account account){
|
||||
ArrayList<String> errors = new ArrayList<>();
|
||||
|
||||
//Check ID
|
||||
if(account.getAccountId() == null){
|
||||
errors.add("Invalid Account ID");
|
||||
}
|
||||
|
||||
//Verify account exists
|
||||
Account existingAccount = accountService.getByAccountId(account.getAccountId());
|
||||
if(existingAccount == null){
|
||||
errors.add("Account not found");
|
||||
}
|
||||
|
||||
//Check login exists in entity
|
||||
if((account.getUsername() == null) || (account.getUsername().isEmpty())){
|
||||
errors.add("Invalid Login ID");
|
||||
}
|
||||
else{
|
||||
//Check login doesn't exist in db, other than the object itself
|
||||
Account existingAccountByLogin = accountService.getByUsername(account.getUsername());
|
||||
if((existingAccountByLogin != null) && (!existingAccountByLogin.getAccountId().equals(account.getAccountId()))){
|
||||
errors.add("Login ID already exists");
|
||||
}
|
||||
}
|
||||
|
||||
//Check email exists in entity
|
||||
if((account.getEmail() == null) || (account.getEmail().isEmpty())){
|
||||
errors.add("Invalid email");
|
||||
}
|
||||
else{
|
||||
//Check email doesn't exist in db, other than the object itself
|
||||
Account existingAccountByEmail = accountService.getByEmail(account.getEmail());
|
||||
if((existingAccountByEmail != null) && (!existingAccountByEmail.getAccountId().equals(account.getAccountId()))){
|
||||
errors.add("Account with email already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.mattrixwv.raidbuilder.controller;
|
||||
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.mattrixwv.raidbuilder.exception.MissingAuthorizationException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class ExceptionController{
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ResponseBody
|
||||
public ObjectNode genericExceptionHandler(Exception error){
|
||||
log.error(error.getMessage(), error);
|
||||
|
||||
|
||||
ObjectNode returnJson = mapper.createObjectNode();
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add(error.getMessage());
|
||||
returnJson.set("errors", errorsNode);
|
||||
returnJson.put("status", "error");
|
||||
|
||||
|
||||
return returnJson;
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthorizationDeniedException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
@ResponseBody
|
||||
public ObjectNode authorizationExceptionHandler(AuthorizationDeniedException error){
|
||||
log.info(error.getMessage());
|
||||
|
||||
|
||||
ObjectNode returnJson = mapper.createObjectNode();
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add(error.getMessage());
|
||||
returnJson.set("errors", errorsNode);
|
||||
returnJson.put("status", "error");
|
||||
|
||||
|
||||
return returnJson;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingAuthorizationException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
@ResponseBody
|
||||
public ObjectNode missingAuthorizationExceptionHandler(MissingAuthorizationException error){
|
||||
log.info(error.getMessage());
|
||||
|
||||
|
||||
ObjectNode returnJson = mapper.createObjectNode();
|
||||
ArrayNode errorsNode = mapper.createArrayNode();
|
||||
errorsNode.add("Server is misconfigured");
|
||||
returnJson.set("errors", errorsNode);
|
||||
returnJson.put("status", "error");
|
||||
|
||||
|
||||
return returnJson;
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/mattrixwv/raidbuilder/entity/Account.java
Normal file
56
src/main/java/com/mattrixwv/raidbuilder/entity/Account.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.mattrixwv.raidbuilder.entity;
|
||||
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "account", schema = "raid_builder")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@NoArgsConstructor
|
||||
public class Account{
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(name = "account_id")
|
||||
private UUID accountId;
|
||||
@Column(name = "username")
|
||||
private String username;
|
||||
@Column(name = "password")
|
||||
private String password;
|
||||
@Column(name = "login_date")
|
||||
private ZonedDateTime loginDate;
|
||||
@Column(name = "email")
|
||||
private String email;
|
||||
@Column(name = "force_reset")
|
||||
private boolean forceReset;
|
||||
@Column(name = "refresh_token")
|
||||
private UUID refreshToken;
|
||||
@Column(name = "refresh_token_expiration")
|
||||
private ZonedDateTime refreshTokenExpiration;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "account_status")
|
||||
private AccountStatus accountStatus;
|
||||
|
||||
@JsonIgnore
|
||||
public String getPassword(){ return password; }
|
||||
@JsonProperty
|
||||
public void setPassword(String password){ this.password = password; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.mattrixwv.raidbuilder.entity;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "account_permission", schema = "raid_builder")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@NoArgsConstructor
|
||||
public class AccountPermission implements GrantedAuthority{
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(name = "account_permission_id")
|
||||
private UUID accountPermissionId;
|
||||
@Column(name = "account_id")
|
||||
private UUID accountId;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "account_permission_type")
|
||||
private AccountPermissionType accountPermissionType;
|
||||
|
||||
|
||||
@Override
|
||||
public String getAuthority(){
|
||||
return accountPermissionType.name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.mattrixwv.raidbuilder.entity;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.TutorialStatus;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "account_tutorial_status", schema = "raid_builder")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@NoArgsConstructor
|
||||
public class AccountTutorialStatus{
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
@Column(name = "account_tutorial_status_id")
|
||||
private UUID accountTutorialStatusId;
|
||||
@Column(name = "account_id")
|
||||
private UUID accountId;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "games_tutorial_status")
|
||||
private TutorialStatus gamesTutorialStatus;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "game_tutorial_status")
|
||||
private TutorialStatus gameTutorialStatus;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "raid_groups_tutorial_status")
|
||||
private TutorialStatus raidGroupsTutorialStatus;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "raid_group_tutorial_status")
|
||||
private TutorialStatus raidGroupTutorialStatus;
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "instance_tutorial_status")
|
||||
private TutorialStatus instanceTutorialStatus;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.mattrixwv.raidbuilder.entity;
|
||||
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public abstract class AuditableEntity{
|
||||
@JsonIgnore
|
||||
@LastModifiedBy
|
||||
@Column(name = "modified_by")
|
||||
protected UUID modifiedBy;
|
||||
@JsonIgnore
|
||||
@LastModifiedDate
|
||||
@Column(name = "modified_date")
|
||||
protected ZonedDateTime modifiedDate;
|
||||
@JsonIgnore
|
||||
@CreatedBy
|
||||
@Column(name = "created_by", updatable = false)
|
||||
protected UUID createdBy;
|
||||
@JsonIgnore
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", updatable = false)
|
||||
protected ZonedDateTime createdDate;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.mattrixwv.raidbuilder.entity;
|
||||
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.mattrixwv.raidbuilder.service.AccountService;
|
||||
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuditableEntityListener{
|
||||
private final AccountService accountService;
|
||||
|
||||
|
||||
@PrePersist
|
||||
public void prePersist(AuditableEntity entity){
|
||||
entity.setCreatedBy(getCurrentUserId());
|
||||
entity.setCreatedDate(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate(AuditableEntity entity){
|
||||
entity.setModifiedBy(getCurrentUserId());
|
||||
entity.setModifiedDate(ZonedDateTime.now());
|
||||
}
|
||||
|
||||
|
||||
private UUID getCurrentUserId(){
|
||||
log.debug("Getting current auditor");
|
||||
|
||||
|
||||
UUID returnUUID;
|
||||
|
||||
try{
|
||||
UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
returnUUID = accountService.getByUsername(userDetails.getUsername()).getAccountId();
|
||||
}
|
||||
catch(Exception e){
|
||||
returnUUID = UUID.fromString("382b1ed8-7d5a-4683-a25d-1f462e9cd921");
|
||||
log.debug("No user logged in: {}", returnUUID);
|
||||
}
|
||||
|
||||
return returnUUID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.mattrixwv.raidbuilder.exception;
|
||||
|
||||
|
||||
public class MissingAuthorizationException extends RuntimeException{
|
||||
public MissingAuthorizationException(){
|
||||
super();
|
||||
}
|
||||
|
||||
public MissingAuthorizationException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MissingAuthorizationException(Throwable cause){
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public MissingAuthorizationException(String message, Throwable cause){
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account;
|
||||
|
||||
|
||||
public interface AccountCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
|
||||
|
||||
public interface AccountRepository extends AccountCustomRepository, JpaRepository<Account, UUID>{
|
||||
public Account findByUsername(String username);
|
||||
public Account findByRefreshToken(UUID refreshToken);
|
||||
public Account findByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
||||
@Repository
|
||||
public class AccountRepositoryImpl implements AccountCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_permission;
|
||||
|
||||
|
||||
public interface AccountPermissionCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_permission;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.AccountPermission;
|
||||
|
||||
|
||||
public interface AccountPermissionRepository extends AccountPermissionCustomRepository, JpaRepository<AccountPermission, UUID>{
|
||||
public List<AccountPermission> findAllByAccountId(UUID accountId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_permission;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
||||
@Repository
|
||||
public class AccountPermissionRepositoryImpl implements AccountPermissionCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_tutorial_status;
|
||||
|
||||
|
||||
public interface AccountTutorialStatusCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_tutorial_status;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.AccountTutorialStatus;
|
||||
|
||||
|
||||
public interface AccountTutorialStatusRepository extends AccountTutorialStatusCustomRepository, JpaRepository<AccountTutorialStatus, UUID>{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mattrixwv.raidbuilder.repository.account_tutorial_status;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
||||
@Repository
|
||||
public class AccountTutorialStatusRepositoryImpl implements AccountTutorialStatusCustomRepository{
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.mattrixwv.raidbuilder.service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.AccountPermission;
|
||||
import com.mattrixwv.raidbuilder.repository.account_permission.AccountPermissionRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@RequiredArgsConstructor
|
||||
public class AccountPermissionService{
|
||||
private final AccountPermissionRepository accountPermissionRepository;
|
||||
|
||||
|
||||
public AccountPermission createAccountPermission(AccountPermission accountPermission){
|
||||
return accountPermissionRepository.save(accountPermission);
|
||||
}
|
||||
|
||||
|
||||
public List<AccountPermission> getByAccountId(UUID accountId){
|
||||
return accountPermissionRepository.findAllByAccountId(accountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.mattrixwv.raidbuilder.service;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
import com.mattrixwv.raidbuilder.entity.AccountPermission;
|
||||
import com.mattrixwv.raidbuilder.entity.AccountTutorialStatus;
|
||||
import com.mattrixwv.raidbuilder.repository.account.AccountRepository;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus;
|
||||
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.TutorialStatus;
|
||||
import com.mattrixwv.raidbuilder.util.UserPrincipal;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@RequiredArgsConstructor
|
||||
public class AccountService implements UserDetailsService{
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AccountRepository accountRepository;
|
||||
private final AccountTutorialStatusService accountTutorialStatusService;
|
||||
//Related services
|
||||
private final AccountPermissionService accountPermissionService;
|
||||
//Fields
|
||||
@Value("${jwt.refreshTokenDuration}")
|
||||
private Duration refreshTokenDuration;
|
||||
|
||||
|
||||
//Write
|
||||
public Account createAccount(Account account){
|
||||
//Set default values
|
||||
account.setAccountStatus(AccountStatus.UNCONFIRMED);
|
||||
account.setPassword(passwordEncoder.encode(account.getPassword()));
|
||||
account.setRefreshToken(UUID.randomUUID());
|
||||
account.setRefreshTokenExpiration(ZonedDateTime.now().plus(refreshTokenDuration));
|
||||
|
||||
//Save account
|
||||
account = accountRepository.save(account);
|
||||
|
||||
//Return the new account
|
||||
return account;
|
||||
}
|
||||
|
||||
public Account confirmAccount(Account account){
|
||||
//Setup the confirmed values
|
||||
account.setRefreshToken(null);
|
||||
account.setRefreshTokenExpiration(null);
|
||||
account.setAccountStatus(AccountStatus.ACTIVE);
|
||||
|
||||
//Save the account
|
||||
account = accountRepository.save(account);
|
||||
|
||||
//Give account default permissions
|
||||
AccountPermission accountPermission = new AccountPermission();
|
||||
accountPermission.setAccountId(account.getAccountId());
|
||||
accountPermission.setAccountPermissionType(AccountPermissionType.USER);
|
||||
accountPermission = accountPermissionService.createAccountPermission(accountPermission);
|
||||
|
||||
//Give account default tutorial actions
|
||||
AccountTutorialStatus accountTutorialStatus = new AccountTutorialStatus();
|
||||
accountTutorialStatus.setAccountId(account.getAccountId());
|
||||
accountTutorialStatus.setGamesTutorialStatus(TutorialStatus.NOT_COMPLETED);
|
||||
accountTutorialStatus.setGameTutorialStatus(TutorialStatus.NOT_COMPLETED);
|
||||
accountTutorialStatus.setRaidGroupsTutorialStatus(TutorialStatus.NOT_COMPLETED);
|
||||
accountTutorialStatus.setRaidGroupTutorialStatus(TutorialStatus.NOT_COMPLETED);
|
||||
accountTutorialStatus.setInstanceTutorialStatus(TutorialStatus.NOT_COMPLETED);
|
||||
accountTutorialStatus = accountTutorialStatusService.createAccountTutorialStatus(accountTutorialStatus);
|
||||
|
||||
//Return the account
|
||||
return account;
|
||||
}
|
||||
|
||||
public Account updateAccount(Account account){
|
||||
return accountRepository.save(account);
|
||||
}
|
||||
|
||||
public Account updatePassword(UUID accountId, String password){
|
||||
Account account = accountRepository.findById(accountId).orElse(null);
|
||||
|
||||
if(account != null){
|
||||
account.setPassword(passwordEncoder.encode(password));
|
||||
account = accountRepository.save(account);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
//Read
|
||||
public Account getByAccountId(UUID accountId){
|
||||
return accountRepository.findById(accountId).orElse(null);
|
||||
}
|
||||
|
||||
public Account getByUsername(String username){
|
||||
return accountRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
public Account getByEmail(String email){
|
||||
return accountRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
public Account getByRefreshToken(UUID refreshToken){
|
||||
return accountRepository.findByRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
|
||||
//! UserDetailsService
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username){
|
||||
Account account = accountRepository.findByUsername(username);
|
||||
|
||||
//If no account with that username exists, throw an exception
|
||||
if(account == null){
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
|
||||
//Update the login timestamp and refresh token
|
||||
account.setLoginDate(ZonedDateTime.now());
|
||||
account.setRefreshToken(UUID.randomUUID());
|
||||
account.setRefreshTokenExpiration(ZonedDateTime.now().plus(refreshTokenDuration));
|
||||
account = accountRepository.save(account);
|
||||
|
||||
//Get the account permissions
|
||||
List<AccountPermission> accountPermissions = accountPermissionService.getByAccountId(account.getAccountId());
|
||||
|
||||
return new UserPrincipal(account, accountPermissions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.mattrixwv.raidbuilder.service;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.AccountTutorialStatus;
|
||||
import com.mattrixwv.raidbuilder.repository.account_tutorial_status.AccountTutorialStatusRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@RequiredArgsConstructor
|
||||
public class AccountTutorialStatusService{
|
||||
private final AccountTutorialStatusRepository accountTutorialStatusRepository;
|
||||
|
||||
|
||||
public AccountTutorialStatus createAccountTutorialStatus(AccountTutorialStatus accountTutorialStatus){
|
||||
return accountTutorialStatusRepository.save(accountTutorialStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.mattrixwv.raidbuilder.util;
|
||||
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
|
||||
@UtilityClass
|
||||
public class DatabaseTypeUtil{
|
||||
public static enum AccountStatus {
|
||||
ACTIVE,
|
||||
LOCKED,
|
||||
INACTIVE,
|
||||
DELETED,
|
||||
UNCONFIRMED
|
||||
};
|
||||
|
||||
public static enum AccountPermissionType {
|
||||
ADMIN,
|
||||
USER
|
||||
}
|
||||
|
||||
public static enum TutorialStatus {
|
||||
COMPLETED,
|
||||
NOT_COMPLETED
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.mattrixwv.raidbuilder.util;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.mattrixwv.raidbuilder.entity.Account;
|
||||
import com.mattrixwv.raidbuilder.entity.AccountPermission;
|
||||
|
||||
|
||||
public class UserPrincipal implements UserDetails{
|
||||
private Account account;
|
||||
private List<AccountPermission> accountPermissions;
|
||||
|
||||
|
||||
public UserPrincipal(@NonNull Account account, @NonNull List<AccountPermission> accountPermissions){
|
||||
this.account = account;
|
||||
this.accountPermissions = accountPermissions;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getUsername(){
|
||||
return account.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword(){
|
||||
return account.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities(){
|
||||
return accountPermissions;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user