Compare commits
101 Commits
279addf8eb
...
renovate/o
| Author | SHA1 | Date | |
|---|---|---|---|
| 20fa7b0e36 | |||
| 2831f03ac1 | |||
| e5ffea3196 | |||
| d0776d7f0b | |||
| 2acafb87eb | |||
| f733b03213 | |||
| 2c3246660e | |||
| d98991c0a0 | |||
| d0f0e72eb2 | |||
| ad675f06f0 | |||
| 90334d15f8 | |||
| 02be0a405e | |||
| 574dd4c093 | |||
| e69462be26 | |||
| 2024c356b0 | |||
| 66233827fd | |||
| 02bd377430 | |||
| a2e69c6a57 | |||
| bbbf34da6e | |||
| e05e523c12 | |||
| 8d6bb14fc1 | |||
| 91415bea5b | |||
| 18ca42a056 | |||
| 8029f2d931 | |||
| d6ffe35d6e | |||
| 8b5ba88d24 | |||
| 86742f1f1a | |||
| 8acc7460a0 | |||
| 656a337501 | |||
| 8eaae7c659 | |||
| 6c077bce27 | |||
| 7a0ee4b438 | |||
| 03ea142db2 | |||
| d285783fe7 | |||
| eced10976b | |||
| eadeb8c518 | |||
| 6ffa22d7b5 | |||
| dacdfd7935 | |||
| a728e376f2 | |||
| 9890086810 | |||
| 2c3c2b783e | |||
| c5c76c77fa | |||
| 992e57a46b | |||
| 638bdd9b7b | |||
| d35063519e | |||
| eb81f6b26a | |||
| 58d59f17e0 | |||
| cce4d4a25d | |||
| b23b6f0dbd | |||
| 62bf6229a7 | |||
| cf297ffe94 | |||
| d27b2054eb | |||
| 178b77e868 | |||
| 23d46a28fb | |||
| dd156d3152 | |||
| cf1c3eefc8 | |||
| 920bddc0db | |||
| 4887902516 | |||
| 9012e61e61 | |||
| 0f9816461f | |||
| ea3860391d | |||
| 17ba5b8b9d | |||
| 9b3f43937f | |||
| 7ffb02bbf0 | |||
| c51486751f | |||
| 211aa26df1 | |||
| 5a706e56ae | |||
| 5175cb89b5 | |||
| a87c692a25 | |||
| c5315daf41 | |||
| 2f6d4a9f1b | |||
| cdcc899453 | |||
| 2cad1c76cc | |||
| d656ab54eb | |||
| 86f4b05b8c | |||
| 17dcc0ac4f | |||
| c0f655b0df | |||
| 9eea30b23e | |||
| 4c79d502e3 | |||
| 3443dcaa95 | |||
| 5bd9ff42ec | |||
| 4990b5b92c | |||
| fea42d33eb | |||
| 72e7af7a35 | |||
| a0fafcc2dc | |||
| 9fa1a4bfba | |||
| 0d9932df45 | |||
| 5c2a2672a8 | |||
| 09455ac694 | |||
| f361fcf5ff | |||
| b88a9f75bf | |||
| f142912471 | |||
| fe03484427 | |||
| a0dfb3fa59 | |||
| c7003c1d02 | |||
| 36c9f4f188 | |||
| 223babece8 | |||
| 33f09270e5 | |||
| 88a9b2b706 | |||
| f85a3982a4 | |||
| 61c0216a3e |
53
.drone.yml
53
.drone.yml
@@ -1,27 +1,41 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
name: verify
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
# test if it compiles correctly
|
||||
- name: build
|
||||
image: maven:3-eclipse-temurin-17
|
||||
image: maven:3-eclipse-temurin-21
|
||||
commands:
|
||||
- mvn verify --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
|
||||
# run unit tests
|
||||
- name: test
|
||||
image: maven:3-eclipse-temurin-17
|
||||
image: maven:3-eclipse-temurin-21
|
||||
commands:
|
||||
- mvn test --no-transfer-progress -B -V
|
||||
|
||||
# check maven dependencies
|
||||
- name: dependency-check
|
||||
image: maven:3-eclipse-temurin-21
|
||||
commands:
|
||||
- mvn dependency-check:check --no-transfer-progress -B -V -DnvdApiKey=$NVD_API_KEY
|
||||
environment:
|
||||
NVD_API_KEY:
|
||||
from_secret: nvd_api_key
|
||||
|
||||
# run code analysis
|
||||
- name: code-analysis
|
||||
image: maven:3-eclipse-temurin-17
|
||||
image: maven:3-eclipse-temurin-21
|
||||
commands:
|
||||
- mvn sonar:sonar --no-transfer-progress -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.host.url=$SONAR_INSTANCE_URL -Dsonar.login=$SONAR_LOGIN_KEY -B -V
|
||||
- mvn sonar:sonar --no-transfer-progress -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.host.url=$SONAR_INSTANCE_URL -Dsonar.token=$SONAR_LOGIN_KEY -B -V
|
||||
environment:
|
||||
SONAR_PROJECT_KEY:
|
||||
from_secret: sonar_project_key
|
||||
@@ -29,3 +43,32 @@ steps:
|
||||
from_secret: sonar_instance_url
|
||||
SONAR_LOGIN_KEY:
|
||||
from_secret: sonar_login_key
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
name: deploy
|
||||
type: docker
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- production
|
||||
|
||||
steps:
|
||||
# skip all previous steps because they were already ran in the "build" phase; we don't need to re-analyze the code.
|
||||
|
||||
# upload to maven repository
|
||||
- name: maven-deploy
|
||||
image: maven:3-eclipse-temurin-21
|
||||
commands:
|
||||
- mvn deploy --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V -gs settings.xml -Dmaven.repo.username=$MAVEN_REPO_USERNAME -Dmaven.repo.password=$MAVEN_REPO_PASSWORD
|
||||
environment:
|
||||
MAVEN_REPO_USERNAME:
|
||||
from_secret: maven_repo_username
|
||||
MAVEN_REPO_PASSWORD:
|
||||
from_secret: maven_repo_password
|
||||
|
||||
92
pom.xml
92
pom.xml
@@ -6,25 +6,107 @@
|
||||
|
||||
<groupId>wtf.beatrice</groupId>
|
||||
<artifactId>release-hive</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.0.2-SNAPSHOT</version>
|
||||
|
||||
<dependencies>
|
||||
<!-- Logging Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.23.1</version>
|
||||
<version>2.25.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.23.1</version>
|
||||
<version>2.25.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Security and Auth -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.13.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Database Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.owasp</groupId>
|
||||
<artifactId>dependency-check-maven</artifactId>
|
||||
<version>12.1.8</version>
|
||||
<configuration>
|
||||
<failBuildOnCVSS>8</failBuildOnCVSS>
|
||||
<nvdApiKey>${nvdApiKey}</nvdApiKey>
|
||||
<formats>
|
||||
<format>html</format>
|
||||
<format>json</format>
|
||||
</formats>
|
||||
<suppressionFiles>
|
||||
<suppressionFile>./suppressions.xml</suppressionFile>
|
||||
</suppressionFiles>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<sonar.dependencyCheck.htmlReportPath>./target/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
|
||||
<sonar.dependencyCheck.jsonReportPath>./target/dependency-check-report.json</sonar.dependencyCheck.jsonReportPath>
|
||||
<sonar.dependencyCheck.summarize>true</sonar.dependencyCheck.summarize>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>nexus-releases</id>
|
||||
<url>https://nexus.beatrice.wtf/repository/maven-releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>nexus-snapshots</id>
|
||||
<url>https://nexus.beatrice.wtf/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
</project>
|
||||
|
||||
33
readme.md
Normal file
33
readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# release-hive back end
|
||||
[](https://drone.beatrice.wtf/bea/release-hive)
|
||||
[](https://sonar.beatrice.wtf/dashboard?id=relhive)
|
||||
[](https://sonar.beatrice.wtf/dashboard?id=relhive)
|
||||
[](https://sonar.beatrice.wtf/dashboard?id=relhive)
|
||||
[](https://sonar.beatrice.wtf/dashboard?id=relhive)
|
||||
|
||||
|
||||
*pure java server backend for software release management*
|
||||
|
||||
## running
|
||||
### database requirements
|
||||
set up a database server with postgresql.
|
||||
this is currently the only supported database backend.
|
||||
|
||||
## building
|
||||
### required tools
|
||||
|
||||
- java 21+ sdk
|
||||
- git
|
||||
- maven
|
||||
|
||||
### build steps
|
||||
the build process currently happens with maven.
|
||||
specific build and release steps have not been specified yet.
|
||||
|
||||
## support
|
||||
| category | info |
|
||||
|---------------------|-------------------------------------------------------------------|
|
||||
| official wiki | [release-hive wiki](https://wiki.beatrice.wtf/en/release-hive) |
|
||||
| official repository | [release-hive src](https://git.beatrice.wtf/bea/release-hive.git) |
|
||||
| build status | [drone-ci](https://drone.beatrice.wtf/bea/release-hive) |
|
||||
| dev email | [hello@beatrice.wtf](mailto:hello@beatrice.wtf) |
|
||||
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
||||
20
settings.xml
Normal file
20
settings.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>nexus-snapshots</id>
|
||||
<username>${maven.repo.username}</username>
|
||||
<password>${maven.repo.password}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>nexus-releases</id>
|
||||
<username>${maven.repo.username}</username>
|
||||
<password>${maven.repo.password}</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
|
||||
49
src/main/java/wtf/beatrice/releasehive/HiveMain.java
Normal file
49
src/main/java/wtf/beatrice/releasehive/HiveMain.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package wtf.beatrice.releasehive;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import wtf.beatrice.releasehive.db.HibernateManager;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootApplication
|
||||
public class HiveMain {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HiveMain.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
LOGGER.info("Registering shutdown hooks");
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
LOGGER.info("Initializing database backend");
|
||||
HibernateManager.initialize();
|
||||
|
||||
LOGGER.info("Initializing Spring Boot");
|
||||
SpringApplication.run(HiveMain.class, args);
|
||||
LOGGER.info("Spring Boot & DB initialized!");
|
||||
|
||||
Session session = HibernateManager.getSession();
|
||||
Transaction transaction = session.beginTransaction();
|
||||
List<User> users = session.createQuery("FROM User", User.class).getResultList();
|
||||
transaction.commit();
|
||||
|
||||
StringBuilder usersListBuilder = new StringBuilder("[");
|
||||
users.forEach(user -> usersListBuilder.append(user.getUsername()).append(","));
|
||||
usersListBuilder.deleteCharAt(usersListBuilder.length() - 1);
|
||||
usersListBuilder.append("]");
|
||||
|
||||
LOGGER.info("Found users: {}", usersListBuilder);
|
||||
LOGGER.info("Total: {}", users.size());
|
||||
}
|
||||
|
||||
private static final Thread shutdownHook = new Thread(() -> {
|
||||
HibernateManager.shutdown();
|
||||
LOGGER.info("Shutting down!");
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package wtf.beatrice.releasehive;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class Main {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Main.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
LOGGER.info("Hello world!");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package wtf.beatrice.releasehive.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import wtf.beatrice.releasehive.repositories.UserRepository;
|
||||
|
||||
@Configuration
|
||||
public class ApplicationConfiguration
|
||||
{
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public ApplicationConfiguration(@Autowired UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return authParameter -> userRepository.findByEmail(authParameter)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationProvider authenticationProvider() {
|
||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||
|
||||
authProvider.setUserDetailsService(userDetailsService());
|
||||
authProvider.setPasswordEncoder(passwordEncoder());
|
||||
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package wtf.beatrice.releasehive.config;
|
||||
|
||||
public class InternalConfiguration
|
||||
{
|
||||
|
||||
private InternalConfiguration() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static final String EMAIL_REGEX_RCF = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
public static final String USERNAME_REGEX = "^[a-zA-Z0-9_-]{3,16}$";
|
||||
public static final String PASSWORD_REGEX = "^.{6,128}$";
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package wtf.beatrice.releasehive.config;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
import wtf.beatrice.releasehive.services.JWTService;
|
||||
import wtf.beatrice.releasehive.services.UserService;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class JWTAuthenticationFilter extends OncePerRequestFilter
|
||||
{
|
||||
private final HandlerExceptionResolver handlerExceptionResolver;
|
||||
|
||||
private final JWTService jwtService;
|
||||
private final UserService userService;
|
||||
|
||||
public JWTAuthenticationFilter(
|
||||
@Autowired JWTService jwtService,
|
||||
@Autowired UserService userService,
|
||||
@Autowired HandlerExceptionResolver handlerExceptionResolver) {
|
||||
this.jwtService = jwtService;
|
||||
this.userService = userService;
|
||||
this.handlerExceptionResolver = handlerExceptionResolver;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final String jwt = authHeader.substring(7);
|
||||
final String email = jwtService.extractEmail(jwt);
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (email != null && authentication == null) {
|
||||
User userDetails = userService.loadUserByEmail(email);
|
||||
|
||||
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (Exception exception) {
|
||||
handlerExceptionResolver.resolveException(request, response, null, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package wtf.beatrice.releasehive.config;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration
|
||||
{
|
||||
|
||||
private final AuthenticationProvider authenticationProvider;
|
||||
private final JWTAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
public SecurityConfiguration(
|
||||
@Autowired JWTAuthenticationFilter jwtAuthenticationFilter,
|
||||
@Autowired AuthenticationProvider authenticationProvider)
|
||||
{
|
||||
this.authenticationProvider = authenticationProvider;
|
||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http.csrf(AbstractHttpConfigurer::disable);
|
||||
|
||||
http.authorizeHttpRequests(registry -> registry
|
||||
.requestMatchers("/api/v1/auth/**")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated());
|
||||
|
||||
http.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
|
||||
http.authenticationProvider(authenticationProvider)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
configuration.setAllowedOrigins(List.of("http://localhost:8080"));
|
||||
configuration.setAllowedMethods(List.of("GET","POST"));
|
||||
configuration.setAllowedHeaders(List.of("Authorization","Content-Type"));
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
|
||||
source.registerCorsConfiguration("/**",configuration);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package wtf.beatrice.releasehive.db;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
public class HibernateManager
|
||||
{
|
||||
private static Session session;
|
||||
private static SessionFactory sessionFactory;
|
||||
|
||||
|
||||
private HibernateManager() {
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if(session != null && !session.isOpen()) {
|
||||
session.close();
|
||||
}
|
||||
if(sessionFactory != null && sessionFactory.isOpen()) {
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Session getSession() {
|
||||
if(session != null && (!session.isOpen() || !session.isConnected())) {
|
||||
session.close();
|
||||
}
|
||||
|
||||
session = sessionFactory.openSession();
|
||||
return session;
|
||||
}
|
||||
|
||||
public static Transaction beginTransaction() {
|
||||
return getSession().beginTransaction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package wtf.beatrice.releasehive.dtos;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EditUsernameAccountDto
|
||||
{
|
||||
private String username;
|
||||
private UUID uuid;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package wtf.beatrice.releasehive.dtos;
|
||||
|
||||
public class LoginUserDto
|
||||
{
|
||||
private String email;
|
||||
|
||||
private String password;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package wtf.beatrice.releasehive.dtos;
|
||||
|
||||
public class RegisterUserDto
|
||||
{
|
||||
private String email;
|
||||
|
||||
private String password;
|
||||
|
||||
private String username;
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package wtf.beatrice.releasehive.exceptions;
|
||||
|
||||
public class HiveException extends Exception
|
||||
{
|
||||
public HiveException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package wtf.beatrice.releasehive.exceptions;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.security.authentication.AccountStatusException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.security.SignatureException;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class RestExceptionHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger(RestExceptionHandler.class);
|
||||
private static final String DESCRIPTION_PROPERTY = "description";
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ProblemDetail handleSecurityException(Exception exception) {
|
||||
ProblemDetail errorDetail = null;
|
||||
|
||||
LOGGER.error(exception);
|
||||
|
||||
if(exception instanceof IllegalArgumentException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(400), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Invalid argument cannot be parsed");
|
||||
}
|
||||
|
||||
if(exception instanceof BadRequestException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(400), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Bad request");
|
||||
}
|
||||
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(401), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Invalid email or password");
|
||||
|
||||
return errorDetail;
|
||||
}
|
||||
|
||||
if (exception instanceof AccountStatusException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Account locked");
|
||||
}
|
||||
|
||||
if (exception instanceof AccessDeniedException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "You are not authorized to access this resource");
|
||||
}
|
||||
|
||||
if (exception instanceof SignatureException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Invalid JWT signature");
|
||||
}
|
||||
|
||||
if (exception instanceof ExpiredJwtException) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Expired JWT token");
|
||||
}
|
||||
|
||||
if (errorDetail == null) {
|
||||
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
|
||||
errorDetail.setProperty(DESCRIPTION_PROPERTY, "Internal server error");
|
||||
}
|
||||
|
||||
return errorDetail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package wtf.beatrice.releasehive.models;
|
||||
|
||||
public class LoginResponse
|
||||
{
|
||||
private String token;
|
||||
|
||||
private long expiresIn;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public LoginResponse setToken(String token) {
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public LoginResponse setExpiresIn(long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginResponse{" +
|
||||
"token='" + token + '\'' +
|
||||
", expiresIn=" + expiresIn +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
114
src/main/java/wtf/beatrice/releasehive/models/User.java
Normal file
114
src/main/java/wtf/beatrice/releasehive/models/User.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package wtf.beatrice.releasehive.models;
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name="users")
|
||||
public class User implements UserDetails
|
||||
{
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID uuid;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String username;
|
||||
|
||||
@Column(unique = true, length = 64, nullable = false)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(updatable = false, name = "created_at")
|
||||
private Date createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private Date updatedAt;
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Date createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(Date updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package wtf.beatrice.releasehive.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, UUID> {
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package wtf.beatrice.releasehive.resources;
|
||||
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import wtf.beatrice.releasehive.dtos.EditUsernameAccountDto;
|
||||
import wtf.beatrice.releasehive.services.AccountService;
|
||||
import wtf.beatrice.releasehive.services.UserService;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/account")
|
||||
public class AccountResource {
|
||||
|
||||
private final AccountService accountService;
|
||||
private final UserService userService;
|
||||
|
||||
public AccountResource(
|
||||
@Autowired AccountService accountService,
|
||||
@Autowired UserService userService) {
|
||||
this.accountService = accountService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
value="/edit",
|
||||
produces= MediaType.TEXT_PLAIN_VALUE)
|
||||
public ResponseEntity<String> register(@RequestBody EditUsernameAccountDto userDto) throws BadRequestException {
|
||||
String username = accountService.changeUsername(userDto);
|
||||
return ResponseEntity.ok(username);
|
||||
}
|
||||
|
||||
@DeleteMapping(
|
||||
value = "/delete/{id}",
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public ResponseEntity<Boolean> deleteUser(@PathVariable("id") String id) {
|
||||
UUID uuid = UUID.fromString(id);
|
||||
boolean deleted = userService.deleteUser(uuid);
|
||||
return ResponseEntity.ok(deleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package wtf.beatrice.releasehive.resources;
|
||||
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import wtf.beatrice.releasehive.dtos.LoginUserDto;
|
||||
import wtf.beatrice.releasehive.dtos.RegisterUserDto;
|
||||
import wtf.beatrice.releasehive.models.LoginResponse;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
import wtf.beatrice.releasehive.services.AccountService;
|
||||
import wtf.beatrice.releasehive.services.JWTService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
public class AuthResource {
|
||||
|
||||
private final AccountService accountService;
|
||||
private final JWTService jwtService;
|
||||
|
||||
public AuthResource(
|
||||
@Autowired AccountService accountService,
|
||||
@Autowired JWTService jwtService) {
|
||||
this.accountService = accountService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
value="/register",
|
||||
produces= MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<User> register(@RequestBody RegisterUserDto userDto) throws BadRequestException {
|
||||
User user = accountService.register(userDto);
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
value="/login",
|
||||
produces= MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginUserDto userDto) throws BadRequestException {
|
||||
User authenticatedUser = accountService.authenticate(userDto);
|
||||
|
||||
String jwtToken = jwtService.generateToken(authenticatedUser);
|
||||
|
||||
LoginResponse loginResponse = new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirationTime());
|
||||
|
||||
return ResponseEntity.ok(loginResponse);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package wtf.beatrice.releasehive.resources;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
import wtf.beatrice.releasehive.services.UserService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/users")
|
||||
public class UserResource
|
||||
{
|
||||
private final UserService userService;
|
||||
|
||||
public UserResource(@Autowired UserService userService)
|
||||
{
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping(
|
||||
value = "/me",
|
||||
produces= MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<User> authenticatedUser() {
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
User currentUser = (User) authentication.getPrincipal();
|
||||
|
||||
return ResponseEntity.ok(currentUser);
|
||||
}
|
||||
|
||||
@GetMapping(
|
||||
value = "/all",
|
||||
produces= MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<List<User>> getAllUsers() {
|
||||
List<User> users = userService.getAllUsers();
|
||||
return ResponseEntity.ok(users);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package wtf.beatrice.releasehive.services;
|
||||
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import wtf.beatrice.releasehive.dtos.EditUsernameAccountDto;
|
||||
import wtf.beatrice.releasehive.dtos.LoginUserDto;
|
||||
import wtf.beatrice.releasehive.dtos.RegisterUserDto;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
|
||||
public interface AccountService
|
||||
{
|
||||
|
||||
User register(RegisterUserDto user) throws BadRequestException;
|
||||
|
||||
User authenticate(LoginUserDto user) throws BadRequestException;
|
||||
|
||||
String changeUsername(EditUsernameAccountDto editData) throws BadRequestException;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package wtf.beatrice.releasehive.services;
|
||||
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import wtf.beatrice.releasehive.config.InternalConfiguration;
|
||||
import wtf.beatrice.releasehive.dtos.EditUsernameAccountDto;
|
||||
import wtf.beatrice.releasehive.dtos.LoginUserDto;
|
||||
import wtf.beatrice.releasehive.dtos.RegisterUserDto;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
import wtf.beatrice.releasehive.repositories.UserRepository;
|
||||
|
||||
@Service
|
||||
public class AccountServiceImpl implements AccountService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
public AccountServiceImpl(
|
||||
@Autowired UserRepository userRepository,
|
||||
@Autowired AuthenticationManager authenticationManager,
|
||||
@Autowired PasswordEncoder passwordEncoder)
|
||||
{
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User register(RegisterUserDto userDto) throws BadRequestException {
|
||||
|
||||
if (userDto.getEmail() == null ||
|
||||
userDto.getEmail().isEmpty() ||
|
||||
userDto.getPassword() == null ||
|
||||
userDto.getPassword().isEmpty() ||
|
||||
userDto.getUsername() == null ||
|
||||
userDto.getUsername().isEmpty()) {
|
||||
|
||||
throw new BadRequestException("Please provide a valid email, password, and username");
|
||||
}
|
||||
|
||||
if(!userDto.getEmail().matches(InternalConfiguration.EMAIL_REGEX_RCF)) {
|
||||
throw new BadRequestException("Invalid email format");
|
||||
}
|
||||
|
||||
if(!userDto.getUsername().matches(InternalConfiguration.USERNAME_REGEX)) {
|
||||
throw new BadRequestException("Username contains invalid characters");
|
||||
}
|
||||
|
||||
if(!userDto.getPassword().matches(InternalConfiguration.PASSWORD_REGEX)) {
|
||||
throw new BadRequestException("Invalid password format");
|
||||
}
|
||||
|
||||
if (userRepository.findByEmail(userDto.getEmail()).isPresent()) {
|
||||
throw new BadRequestException("An account already exists with this email");
|
||||
}
|
||||
|
||||
if(userRepository.findByUsername(userDto.getUsername()).isPresent()) {
|
||||
throw new BadRequestException("Username already in use");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(userDto.getUsername());
|
||||
user.setEmail(userDto.getEmail());
|
||||
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User authenticate(LoginUserDto userDto) throws BadRequestException {
|
||||
|
||||
if (userDto.getEmail() == null ||
|
||||
userDto.getEmail().isEmpty() ||
|
||||
userDto.getPassword() == null ||
|
||||
userDto.getPassword().isEmpty()) {
|
||||
|
||||
throw new BadRequestException("Please provide a valid email and password");
|
||||
}
|
||||
|
||||
if(!userDto.getEmail().matches(InternalConfiguration.EMAIL_REGEX_RCF)) {
|
||||
throw new BadRequestException("Invalid email format");
|
||||
}
|
||||
|
||||
if(!userDto.getPassword().matches(InternalConfiguration.PASSWORD_REGEX)) {
|
||||
throw new BadRequestException("Invalid password format");
|
||||
}
|
||||
|
||||
if (!userRepository.findByEmail(userDto.getEmail()).isPresent()) {
|
||||
throw new BadRequestException("No account registered with this email");
|
||||
}
|
||||
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userDto.getEmail(),
|
||||
userDto.getPassword()));
|
||||
|
||||
return userRepository.findByEmail(userDto.getEmail())
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changeUsername(EditUsernameAccountDto editData) {
|
||||
User user = userRepository
|
||||
.findById(editData.getUuid())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
user.setUsername(editData.getUsername());
|
||||
userRepository.save(user);
|
||||
return user.getUsername();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package wtf.beatrice.releasehive.services;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
public class JWTService
|
||||
{
|
||||
@Value("${security.jwt.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${security.jwt.expiration-time}")
|
||||
private long jwtExpiration;
|
||||
|
||||
public String extractEmail(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
public String generateToken(User userDetails) {
|
||||
return generateToken(new HashMap<>(), userDetails);
|
||||
}
|
||||
|
||||
public String generateToken(Map<String, Object> extraClaims, User userDetails) {
|
||||
return buildToken(extraClaims, userDetails, jwtExpiration);
|
||||
}
|
||||
|
||||
public long getExpirationTime() {
|
||||
return jwtExpiration;
|
||||
}
|
||||
|
||||
private String buildToken(
|
||||
Map<String, Object> extraClaims,
|
||||
User userDetails,
|
||||
long expiration
|
||||
) {
|
||||
return Jwts
|
||||
.builder()
|
||||
.claims(extraClaims)
|
||||
.subject(userDetails.getEmail())
|
||||
.issuedAt(new Date(System.currentTimeMillis()))
|
||||
.expiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSignInKey(), Jwts.SIG.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token, User userDetails) {
|
||||
final String email = extractEmail(token);
|
||||
return (email.equals(userDetails.getEmail())) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts
|
||||
.parser()
|
||||
.verifyWith(getSignInKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
private SecretKey getSignInKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package wtf.beatrice.releasehive.services;
|
||||
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface UserService
|
||||
{
|
||||
List<User> getAllUsers();
|
||||
|
||||
User loadUserByUsername(String username) throws UsernameNotFoundException;
|
||||
|
||||
User loadUserByEmail(String email) throws UsernameNotFoundException;
|
||||
|
||||
boolean deleteUser(UUID id);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package wtf.beatrice.releasehive.services;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import wtf.beatrice.releasehive.models.User;
|
||||
import wtf.beatrice.releasehive.repositories.UserRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService
|
||||
{
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public UserServiceImpl(@Autowired UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getAllUsers() {
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
return userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public User loadUserByEmail(String email) throws UsernameNotFoundException {
|
||||
return userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(email));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteUser(UUID id) {
|
||||
userRepository.delete(userRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
26
src/main/java/wtf/beatrice/releasehive/utils/JsonUtil.java
Normal file
26
src/main/java/wtf/beatrice/releasehive/utils/JsonUtil.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package wtf.beatrice.releasehive.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class JsonUtil
|
||||
{
|
||||
|
||||
private JsonUtil() {
|
||||
throw new AssertionError("Utility class");
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(JsonUtil.class);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public static String convertToJson(Object input) {
|
||||
try {
|
||||
return MAPPER.writeValueAsString(input);
|
||||
} catch (JsonProcessingException e) {
|
||||
LOGGER.error(e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/resources/application.properties
Normal file
13
src/main/resources/application.properties
Normal file
@@ -0,0 +1,13 @@
|
||||
server.port=8080
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/releasehive
|
||||
spring.datasource.username=relhive
|
||||
spring.datasource.password=beelover
|
||||
|
||||
## Hibernate properties
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.open-in-view=false
|
||||
|
||||
security.jwt.secret-key=ed725256582a23e94f81ba36d7df498ea330c7ba978e2d8876fe135b4bb34068
|
||||
# 1h in millisecond
|
||||
security.jwt.expiration-time=3600000
|
||||
5
src/main/resources/application.yml
Normal file
5
src/main/resources/application.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
|
||||
28
src/main/resources/hibernate.cfg.xml
Normal file
28
src/main/resources/hibernate.cfg.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
|
||||
<!-- Connection settings -->
|
||||
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
|
||||
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/releasehive</property>
|
||||
<property name="hibernate.connection.username">relhive</property>
|
||||
<property name="hibernate.connection.password">development</property>
|
||||
|
||||
<!-- SQL dialect -->
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
|
||||
|
||||
<!-- Print executed SQL to stdout -->
|
||||
<property name="show_sql">true</property>
|
||||
|
||||
<!-- Update database on startup -->
|
||||
<property name="hibernate.hbm2ddl.auto">update</property>
|
||||
|
||||
<!-- Annotated entity classes -->
|
||||
<mapping class="wtf.beatrice.releasehive.models.User"/>
|
||||
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
||||
10
suppressions.xml
Normal file
10
suppressions.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
file name: snakeyaml-1.33.jar
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
|
||||
<cve>CVE-2021-4235</cve>
|
||||
</suppress>
|
||||
</suppressions>
|
||||
Reference in New Issue
Block a user