Authorization working
This commit is contained in:
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Spring Boot-RaidBuilderAPI<raid-builder-api>",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"mainClass": "com.mattrixwv.raidbuilder.RaidBuilderAPI",
|
||||
"projectName": "raid-builder-api",
|
||||
"args": "--spring.config.additional-location=local/application.properties --logging.config=local/log4j2-spring.xml",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"raidbuilder",
|
||||
"springframework"
|
||||
]
|
||||
}
|
||||
25
SpringStuffToIgnoreInExceptions.txt
Normal file
25
SpringStuffToIgnoreInExceptions.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
java.lang.reflect.Method
|
||||
org.apache.cataline
|
||||
org.springframework.aop
|
||||
org.springframework.security
|
||||
org.springframework.transaction
|
||||
org.springframework.web
|
||||
sun.reflect
|
||||
net.sf.cglib
|
||||
ByCGLIB
|
||||
//Thsea re ones I'm removing because I'm not seeing anythign important in them
|
||||
BySpringCGLIB //Spring impl of CGLIB?
|
||||
org.springframework.cglib
|
||||
org.springframework.orm //Spring automagic
|
||||
org.springframework.dao //Spring automagic
|
||||
org.springframework.session //Spring automagic
|
||||
org.springframework.repository //Spring automagic
|
||||
org.springframework.data //Spring automagic
|
||||
org.springframework.scheduling //Spring automagic scheduling
|
||||
org.springframework.beans //Spring automagic beans
|
||||
org.springframework.boot //Spring Boot automagic
|
||||
jdk.internal.reflect //Java automagic
|
||||
sun.proxy //Java automagic
|
||||
io.netty.util.concurrent //Server framework threading
|
||||
com.google.common.util.concurrent //Threading
|
||||
java.util.concurrent //Threading
|
||||
4
db/1.0.0/1. schema.sql
Normal file
4
db/1.0.0/1. schema.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
CREATE USER raid_builder WITH PASSWORD 'raid_builder';
|
||||
|
||||
CREATE SCHEMA raid_builder;
|
||||
GRANT ALL ON SCHEMA raid_builder TO raid_builder;
|
||||
16
db/1.0.0/2. createAccount.sql
Normal file
16
db/1.0.0/2. createAccount.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
CREATE TYPE raid_builder.account_status AS ENUM ( 'ACTIVE', 'LOCKED', 'INACTIVE', 'DELETED', 'UNCONFIRMED');
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS raid_builder.account(
|
||||
account_id uuid PRIMARY KEY,
|
||||
username text UNIQUE NOT NULL,
|
||||
password text NOT NULL,
|
||||
login_date timestamptz,
|
||||
email text UNIQUE NOT NULL,
|
||||
force_reset boolean NOT NULL,
|
||||
refresh_token uuid UNIQUE,
|
||||
refresh_token_expiration timestamptz,
|
||||
account_status raid_builder.account_status NOT NULL
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE raid_builder.account TO raid_builder;
|
||||
10
db/1.0.0/3. createAccountPermission.sql
Normal file
10
db/1.0.0/3. createAccountPermission.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TYPE raid_builder.account_permission_type AS ENUM ( 'ADMIN', 'USER' );
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS raid_builder.account_permission(
|
||||
account_permission_id uuid PRIMARY KEY,
|
||||
account_id uuid REFERENCES raid_builder.account(account_id) NOT NULL,
|
||||
account_permission_type raid_builder.account_permission_type NOT NULL
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE raid_builder.account_permission TO raid_builder;
|
||||
14
db/1.0.0/4. createAccountTutorialStatus.sql
Normal file
14
db/1.0.0/4. createAccountTutorialStatus.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
CREATE TYPE raid_builder.tutorial_status AS ENUM ( 'COMPLETED', 'NOT_COMPLETED' );
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS raid_builder.account_tutorial_status (
|
||||
account_tutorial_status_id uuid PRIMARY KEY,
|
||||
account_id uuid REFERENCES raid_builder.account(account_id),
|
||||
games_tutorial_status raid_builder.tutorial_status NOT NULL,
|
||||
game_tutorial_status raid_builder.tutorial_status NOT NULL,
|
||||
raid_groups_tutorial_status raid_builder.tutorial_status NOT NULL,
|
||||
raid_group_tutorial_status raid_builder.tutorial_status NOT NULL,
|
||||
instance_tutorial_status raid_builder.tutorial_status NOT NULL
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE raid_builder.account_tutorial_status TO raid_builder;
|
||||
144
pom.xml
Normal file
144
pom.xml
Normal file
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.mattrixwv.raidbuilder</groupId>
|
||||
<artifactId>raid-builder-api</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>Raid Builder API</name>
|
||||
<url>https://api.raidbuilder.mattrixwv.com</url>
|
||||
|
||||
<properties>
|
||||
<!--Compile-->
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<java.version>21</java.version>
|
||||
|
||||
<!--Sonarqube-->
|
||||
<sonar.java.source>21</sonar.java.source>
|
||||
<sonar.dependencyCheck.jsonReportPath>target/dependency-check-report.json</sonar.dependencyCheck.jsonReportPath>
|
||||
<sonar.dependencyCheck.htmlReportPath>target/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!--! Spring Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--! Database -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.hypersistence</groupId>
|
||||
<artifactId>hypersistence-utils-hibernate-63</artifactId>
|
||||
<version>3.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--! Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hibernate6</artifactId>
|
||||
<version>2.18.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--! Boilerplate Generator -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--! Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-layout-template-json</artifactId>
|
||||
<version>2.24.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<version>4.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "jwt",
|
||||
"description": "Properties for JWT management"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "allowed-origins",
|
||||
"type": "java.lang.String",
|
||||
"description": "Allowed CORS origins",
|
||||
"defaultValue": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"name": "jwt.access-token-duration",
|
||||
"type": "java.time.Duration",
|
||||
"description": "The duration for which the access token is valid",
|
||||
"defaultValue": "900s"
|
||||
},
|
||||
{
|
||||
"name": "jwt.refresh-token-duration",
|
||||
"type": "java.time.Duration",
|
||||
"description": "The duration for which the refresh token is valid",
|
||||
"defaultValue": "30d"
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "allowed-origins",
|
||||
"values": [
|
||||
{
|
||||
"value": "http://localhost:3000",
|
||||
"description": "Local development"
|
||||
},
|
||||
{
|
||||
"value": "https://api.raidbuilder.mattrixwv.com",
|
||||
"description": "Production"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
21
src/main/resources/application.properties
Normal file
21
src/main/resources/application.properties
Normal file
@@ -0,0 +1,21 @@
|
||||
#Server options
|
||||
server.error.include-stacktrace=always
|
||||
server.port=8001
|
||||
server.shutdown=graceful
|
||||
spring.lifecycle.timeoutPerShutdownPhase=10s
|
||||
|
||||
#Database options
|
||||
spring.jpa.openInView=false
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/db_name?stringtype=unspecified
|
||||
spring.datasource.username=username
|
||||
spring.datasource.password=password
|
||||
|
||||
#CORS
|
||||
#allowedOrigins=http://localhost:3000
|
||||
allowedOrigins=*
|
||||
|
||||
#JWT
|
||||
rsa.privateKey=classpath:certs/private.pem
|
||||
rsa.publicKey=classpath:certs/public.pem
|
||||
jwt.accessTokenDuration=15m
|
||||
jwt.refreshTokenDuration=30d
|
||||
28
src/main/resources/certs/keypair.pem
Normal file
28
src/main/resources/certs/keypair.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTPsOQkPNJbPi4
|
||||
B2dlDKunPOL1Ehf7DXLDSablGzsunJk366or5AI1QbiCGeKU/jhTjsbZxXfcBVgt
|
||||
n4BWxnaY6Ypay344LI/YYuSRieENcvZZuz9+kFwACpbO8+NIW1pGiPczOpUzFcSn
|
||||
4BD36RnukeFOyDCiDlK82PF37hU/v79DAdQ7UAwK7M5xuzUbdRGebI7wRr++qbz5
|
||||
9vcqLRqPY8JkEn0dmQ/7kOwRvqFBoCCVoVHThKPNWKxfkjTGKhrUGvAeUwl0A8MP
|
||||
KNWDnLQ2HRfAS5UYh7BZ0Pp3CJupU6j3EzP+LNiKV7tQhZcGMcLclxjLNU6vMkS8
|
||||
aiQFW7LfAgMBAAECggEAXEqBFZGGgQ9XcMtnTFonSocK3yg7CueauqBchp8JkblA
|
||||
JZLUA533qv3eFxUpDZAt2q+3x+ACmEFLf48+emr12KO72yQprnAlnlPCaaV0CjSu
|
||||
VZC90lVOpIP71EnwhCXJQKTJX3vaQHnjs7Zso2sXdcgNSCalPMAGPNSJVqzRYspf
|
||||
mJxJbDn1iaZVfvBTsfbJQ02TXNA47UkPrQD/pBDegT34e0niLFTD4KY02OHSFg80
|
||||
uKY/pGvGpdGuJkrkdv6wL5q81zDNp7YtGxFbVdtEGY3vLJgWwobI9sspnJKJ00Fr
|
||||
HwYWUXPZQSwsU+I7jYI3/e+MRozola+EQCNmNJhXyQKBgQDm8t6h3jqkUq96+wXz
|
||||
NYdMKSfE4zOk3CnZoq+iLXltC8B7Sns+y0+kuyHwlBsRGu1fQJwt2io2Vbxr94Qd
|
||||
3YypprHE2WYDUFHwrHejcon8TwuB0zSllZE/zI+tWgma+1/hpCn4N7J8P37dRHTY
|
||||
FvFU6U6qED3lSARjJo/tTbud9wKBgQDqKMEAzLdUy5opliwwG4+WAgtg2oNdWxFL
|
||||
SFv1ErpHrlOILaoPVKb3Mfv68Ku/rpe6lmaNqasVLx1c3roh2nJhk8++bY+Y+JZu
|
||||
25rIGO1BpcXobYzIQ7bjTYuvkFLDBmSy29NUaVyRT8h8114FwxurRSCif0jMp66v
|
||||
KEjxjhp4WQKBgQCtM6vf/YhBQHm2Y5gcxCJJ7fuTX0mV9D+2ppnNqQkNzOh4Dm3L
|
||||
tDJwup9Di++YrncjHpOCl8FcqoP6/NAqjcM2YHulw90L0ysAsnevLvFpNebNYJZ1
|
||||
MGyUSlfejE3z214XHUUUkMDdCcmdK//tJ5eqNKb4R+IDmDUiHwOF1uxEFQKBgQDk
|
||||
yRbykhLaXehtk5XvFy6u0aaOZlINx+nY1YVLqZWqbdCd8IgFXJ+aTRM3dylIKu2C
|
||||
2GqxJULMevFEiTXx178ESeij1eaE/vX0sMrFkV1XVAJPe6IfFdI+usitq+TBOqDv
|
||||
BMux4RQZwotQNxldpemF6Q/e1WCq3XdXGpRSt5ZzWQKBgCkzJi9/Cs4qn00iyhFC
|
||||
h3dXfq3Des/ZYxGBJpw2/SAOBIti+QKQS7/UlPAml789cEO+B9w+dDrIENf4VJF0
|
||||
0Zar2hI85bEnpUy+XeRwSo9vhgaYIox4k7B/FET4plqM1A6TyTGRy987CW9ua7ny
|
||||
rQ9EYSbPs55Z4Rcv4O+DxAAL
|
||||
-----END PRIVATE KEY-----
|
||||
28
src/main/resources/certs/private.pem
Normal file
28
src/main/resources/certs/private.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTPsOQkPNJbPi4
|
||||
B2dlDKunPOL1Ehf7DXLDSablGzsunJk366or5AI1QbiCGeKU/jhTjsbZxXfcBVgt
|
||||
n4BWxnaY6Ypay344LI/YYuSRieENcvZZuz9+kFwACpbO8+NIW1pGiPczOpUzFcSn
|
||||
4BD36RnukeFOyDCiDlK82PF37hU/v79DAdQ7UAwK7M5xuzUbdRGebI7wRr++qbz5
|
||||
9vcqLRqPY8JkEn0dmQ/7kOwRvqFBoCCVoVHThKPNWKxfkjTGKhrUGvAeUwl0A8MP
|
||||
KNWDnLQ2HRfAS5UYh7BZ0Pp3CJupU6j3EzP+LNiKV7tQhZcGMcLclxjLNU6vMkS8
|
||||
aiQFW7LfAgMBAAECggEAXEqBFZGGgQ9XcMtnTFonSocK3yg7CueauqBchp8JkblA
|
||||
JZLUA533qv3eFxUpDZAt2q+3x+ACmEFLf48+emr12KO72yQprnAlnlPCaaV0CjSu
|
||||
VZC90lVOpIP71EnwhCXJQKTJX3vaQHnjs7Zso2sXdcgNSCalPMAGPNSJVqzRYspf
|
||||
mJxJbDn1iaZVfvBTsfbJQ02TXNA47UkPrQD/pBDegT34e0niLFTD4KY02OHSFg80
|
||||
uKY/pGvGpdGuJkrkdv6wL5q81zDNp7YtGxFbVdtEGY3vLJgWwobI9sspnJKJ00Fr
|
||||
HwYWUXPZQSwsU+I7jYI3/e+MRozola+EQCNmNJhXyQKBgQDm8t6h3jqkUq96+wXz
|
||||
NYdMKSfE4zOk3CnZoq+iLXltC8B7Sns+y0+kuyHwlBsRGu1fQJwt2io2Vbxr94Qd
|
||||
3YypprHE2WYDUFHwrHejcon8TwuB0zSllZE/zI+tWgma+1/hpCn4N7J8P37dRHTY
|
||||
FvFU6U6qED3lSARjJo/tTbud9wKBgQDqKMEAzLdUy5opliwwG4+WAgtg2oNdWxFL
|
||||
SFv1ErpHrlOILaoPVKb3Mfv68Ku/rpe6lmaNqasVLx1c3roh2nJhk8++bY+Y+JZu
|
||||
25rIGO1BpcXobYzIQ7bjTYuvkFLDBmSy29NUaVyRT8h8114FwxurRSCif0jMp66v
|
||||
KEjxjhp4WQKBgQCtM6vf/YhBQHm2Y5gcxCJJ7fuTX0mV9D+2ppnNqQkNzOh4Dm3L
|
||||
tDJwup9Di++YrncjHpOCl8FcqoP6/NAqjcM2YHulw90L0ysAsnevLvFpNebNYJZ1
|
||||
MGyUSlfejE3z214XHUUUkMDdCcmdK//tJ5eqNKb4R+IDmDUiHwOF1uxEFQKBgQDk
|
||||
yRbykhLaXehtk5XvFy6u0aaOZlINx+nY1YVLqZWqbdCd8IgFXJ+aTRM3dylIKu2C
|
||||
2GqxJULMevFEiTXx178ESeij1eaE/vX0sMrFkV1XVAJPe6IfFdI+usitq+TBOqDv
|
||||
BMux4RQZwotQNxldpemF6Q/e1WCq3XdXGpRSt5ZzWQKBgCkzJi9/Cs4qn00iyhFC
|
||||
h3dXfq3Des/ZYxGBJpw2/SAOBIti+QKQS7/UlPAml789cEO+B9w+dDrIENf4VJF0
|
||||
0Zar2hI85bEnpUy+XeRwSo9vhgaYIox4k7B/FET4plqM1A6TyTGRy987CW9ua7ny
|
||||
rQ9EYSbPs55Z4Rcv4O+DxAAL
|
||||
-----END PRIVATE KEY-----
|
||||
9
src/main/resources/certs/public.pem
Normal file
9
src/main/resources/certs/public.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0z7DkJDzSWz4uAdnZQyr
|
||||
pzzi9RIX+w1yw0mm5Rs7LpyZN+uqK+QCNUG4ghnilP44U47G2cV33AVYLZ+AVsZ2
|
||||
mOmKWst+OCyP2GLkkYnhDXL2Wbs/fpBcAAqWzvPjSFtaRoj3MzqVMxXEp+AQ9+kZ
|
||||
7pHhTsgwog5SvNjxd+4VP7+/QwHUO1AMCuzOcbs1G3URnmyO8Ea/vqm8+fb3Ki0a
|
||||
j2PCZBJ9HZkP+5DsEb6hQaAglaFR04SjzVisX5I0xioa1BrwHlMJdAPDDyjVg5y0
|
||||
Nh0XwEuVGIewWdD6dwibqVOo9xMz/izYile7UIWXBjHC3JcYyzVOrzJEvGokBVuy
|
||||
3wIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
8
src/main/resources/generateCerts.sh
Normal file
8
src/main/resources/generateCerts.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#Create RSA key pair
|
||||
openssl genrsa -out keypair.pem 2048
|
||||
|
||||
#Extract public key
|
||||
openssl rsa -in keypair.pem -pubout -out public.pem
|
||||
|
||||
#Create private key in PKCS#8 format
|
||||
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out private.pem
|
||||
39
src/main/resources/log4j2-spring.xml
Normal file
39
src/main/resources/log4j2-spring.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--<Configuration strict="true" xmlns="http://logging.apache.org/log4j/2.0/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://logging.apache.org/log4j/2.0/config https://raw.githubusercontent.com/apache/logging-log4j2/master/log4j-core/src/main/resources/Log4j-config.xsd">-->
|
||||
<Configuration>
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout disableAnsi="false">
|
||||
<Pattern>
|
||||
%style{%d{MM-dd-yyyy HH:mm:ss.SSSZ}}{bright, black} %highlight{%5p} %style{function=}{bright, black}%style{%50.50replace{%c{0}.%M}{}{}}{blue, bright} %style{requestId=%36.36X{requestId}}{bright, black} %style{username=%X{username}}{bright, black} %highlight{---} %message%n
|
||||
</Pattern>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
|
||||
|
||||
<RollingRandomAccessFile name="file" fileName="cipherStreamAPI.log" filePattern="%d{MM-dd-yyyy}-cipherStreamAPI.log.gz" immediateFlush="true">
|
||||
<JsonTemplateLayout eventTemplateUri="classpath:template.json"></JsonTemplateLayout>
|
||||
<Policies>
|
||||
<SizeBasedTriggeringPolicy size="10MB"/>
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="20"/>
|
||||
</RollingRandomAccessFile>
|
||||
|
||||
|
||||
<Console name="consoleJSON" target="SYSTEM_OUT">
|
||||
<JsonTemplateLayout eventTemplateUri="classpath:template.json"/>
|
||||
</Console>
|
||||
|
||||
|
||||
<Socket name="graylog" host="loggingpi.mattrixwv.com" port="1502">
|
||||
<JsonTemplateLayout eventTemplateUri="classpath:template.json"/>
|
||||
</Socket>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info" includeLocation="true">
|
||||
<AppenderRef ref="console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
46
src/main/resources/template.json
Normal file
46
src/main/resources/template.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"msg_timestamp": {
|
||||
"$resolver": "timestamp",
|
||||
"pattern": {
|
||||
"format": "yyyy-MM-dd HH:mm:ss.SSSZ"
|
||||
}
|
||||
},
|
||||
"level": {
|
||||
"$resolver": "level",
|
||||
"field": "name"
|
||||
},
|
||||
"requestId": {
|
||||
"$resolver": "mdc",
|
||||
"key": "requestId"
|
||||
},
|
||||
"logger": {
|
||||
"$resolver": "logger",
|
||||
"field": "name"
|
||||
},
|
||||
"mdc":{
|
||||
"$resolver": "mdc",
|
||||
"flatten": false,
|
||||
"stringified": false
|
||||
},
|
||||
"message": {
|
||||
"$resolver": "message",
|
||||
"stringified": true
|
||||
},
|
||||
"exception": {
|
||||
"exception_class": {
|
||||
"$resolver": "exception",
|
||||
"field": "className"
|
||||
},
|
||||
"exception_message": {
|
||||
"$resolver": "exception",
|
||||
"field": "message"
|
||||
},
|
||||
"stacktrace": {
|
||||
"$resolver": "exception",
|
||||
"field": "stackTrace",
|
||||
"stackTrace": {
|
||||
"stringified": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
version-rules.xml
Normal file
17
version-rules.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset xmlns="http://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" comparisonMethod="maven"
|
||||
xsi:schemaLocation="http://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0 https://www.mojohaus.org/versions-maven-plugin/xsd/rule-2.0.0.xsd">
|
||||
<ignoreVersions>
|
||||
<!-- Ignore Alpha's, Beta's, release candidates and milestones -->
|
||||
<ignoreVersion type="regex">(?i).*Alpha(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*a(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*Beta(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*-B(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*RC(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*CR(?:-?\d+)?</ignoreVersion>
|
||||
<ignoreVersion type="regex">(?i).*M(?:-?\d+)?</ignoreVersion>
|
||||
</ignoreVersions>
|
||||
<rules>
|
||||
</rules>
|
||||
</ruleset>
|
||||
Reference in New Issue
Block a user