Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented user authentication #32

Merged
merged 36 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9c7e99e
[Feature-19-implement-security][WIP] Implemented spring security 6. G…
Alekseeybg Sep 13, 2023
0ab3537
[Feature-19-implement-security][WIP] Changed error 403 Forbidden to 4…
Alekseeybg Sep 13, 2023
8130a0f
[Feature-19-implement-security][WIP] More appropriate error 403 Forbi…
Alekseeybg Sep 13, 2023
f18c9d7
[Feature-19-implement-security][WIP] Created AuthenticatedIntegration…
Alekseeybg Sep 15, 2023
4c03d7b
[Feature-19-implement-security][WIP] Fixed code smells pointed by son…
Alekseeybg Sep 15, 2023
1a4ce5d
[Feature-19-implement-security][WIP] Reverted fix of one code smell. …
Alekseeybg Sep 15, 2023
f6f3d64
[Feature-19-implement-security][WIP] Hardcoded test secretKey in test…
Alekseeybg Sep 15, 2023
582fdee
Merge pull request #31 from JewelleryManagement/test
VladoKat Sep 19, 2023
35fb3b0
[Feature-19-implement-security][WIP] Refactored parts of code. Added …
Alekseeybg Sep 20, 2023
8403f02
[Feature-19-implement-security][WIP] Refactored code. Added integrati…
Alekseeybg Sep 25, 2023
44defb7
[Feature-19-implement-security][WIP] Refactored code. More exceptions…
Alekseeybg Sep 26, 2023
55107b3
[Feature-19-implement-security][WIP] Fixed (tried) code smells. Small…
Alekseeybg Sep 27, 2023
cb717f4
Merge branch 'main' into feature-19-user-authentication
Alekseeybg Sep 27, 2023
abc4152
[Feature-19-implement-security][WIP] Fixed failing tests to work afte…
Alekseeybg Sep 27, 2023
fe4e610
[Feature-19-implement-security][WIP] Fixed failing integration test
Alekseeybg Sep 27, 2023
a46b54f
[Feature-19-implement-security][WIP] Fixed custom exception messages …
Alekseeybg Sep 27, 2023
4863da9
[Feature-19-implement-security][WIP] Fixed final code smells
Alekseeybg Sep 27, 2023
d564bfc
[Feature-19-implement-security][WIP] Added more unit tests
Alekseeybg Sep 27, 2023
0d1a822
[Feature-19-implement-security][WIP] Removed code smells
Alekseeybg Sep 27, 2023
c3e8b98
[Feature-19-implement-security][WIP] Fixes according to code review. …
Alekseeybg Sep 28, 2023
84c8b7b
[Feature-19-implement-security][WIP] fix .gitignore Changed logic to …
Alekseeybg Sep 29, 2023
01da5c3
[Feature-19-implement-security][WIP] fix .gitignore
Alekseeybg Sep 29, 2023
9f0e5dd
[Feature-19-implement-security][WIP] fix .gitignore
Alekseeybg Sep 29, 2023
3e15dfa
[Feature-19-implement-security][WIP] small refactor to AuthService cl…
Alekseeybg Sep 29, 2023
dabb227
[Feature-19-implement-security][WIP] Added more tests for higher cove…
Alekseeybg Sep 29, 2023
8c5d5d6
[Feature-19-implement-security][WIP] Test coverage
Alekseeybg Oct 2, 2023
b9a4f77
[Feature-19-implement-security][WIP] Refactored code. Added tests
Alekseeybg Oct 2, 2023
ba21727
[Feature-19-implement-security][WIP] More tests
Alekseeybg Oct 2, 2023
829f88a
[Feature-19-implement-security][WIP] More tests and refactor
Alekseeybg Oct 2, 2023
2358404
[Feature-19-implement-security][WIP] More tests.
Alekseeybg Oct 2, 2023
2b0c390
[Feature-19-implement-security][WIP] More tests.
Alekseeybg Oct 2, 2023
448bd5c
[Feature-19-implement-security][WIP] Fix CORS problem
Alekseeybg Oct 3, 2023
8b4436d
[Feature-19-implement-security][WIP] Fix CORS problem. Fixed integrat…
Alekseeybg Oct 3, 2023
512af28
[Feature-19-implement-security] Fixed review issues
Alekseeybg Oct 4, 2023
aea23f4
[Feature-19-implement-security] Fixed last small issue.
Alekseeybg Oct 4, 2023
6a7871d
[feature-19-user-authentication] update README with startup instructi…
VladoKat Oct 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ build/

# Gradle files
.gradle/
build/

# Spring Boot properties
.env
/src/main/resources/data.sql

pom.xml.tag
pom.xml.releaseBackup
Expand Down
36 changes: 33 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,44 @@
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<testcontainers.version>1.18.3</testcontainers.version>
<lombok.version>1.18.22</lombok.version>
<lombok-mapstruct-binding>0.2.0</lombok-mapstruct-binding>
<lombok-mapstruct-binding-version>0.2.0</lombok-mapstruct-binding-version>
<jjwt-version>0.11.5</jjwt-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt-version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt-version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt-version}</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
Expand All @@ -51,7 +81,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding}</version>
<version>${lombok-mapstruct-binding-version}</version>
</dependency>

<!-- snakeyaml 2.0 dependency is only added to fix vulnerability coming from
Expand Down Expand Up @@ -158,7 +188,7 @@
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding}</version>
<version>${lombok-mapstruct-binding-version}</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/jewellery/inventory/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package jewellery.inventory.controller;

import jakarta.validation.Valid;
import jewellery.inventory.dto.request.AuthenticationRequestDto;
import jewellery.inventory.dto.response.UserAuthDetailsDto;
import jewellery.inventory.service.security.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/login")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public UserAuthDetailsDto login(@Valid @RequestBody AuthenticationRequestDto authRequest) {
return authService.login(authRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package jewellery.inventory.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "${cors.origins}")
public class HomeController {

@GetMapping("/home")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

@RestController
@RequestMapping("/resources")
@CrossOrigin(origins = "${cors.origins}")
@RequiredArgsConstructor
public class ResourceController {
private final ResourceService resourceService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import jewellery.inventory.service.ResourceInUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -20,7 +19,6 @@

@RestController
@RequestMapping("/resources/availability")
@CrossOrigin(origins = "${cors.origins}")
@RequiredArgsConstructor
public class ResourceInUserController {
private final ResourceInUserService resourceAvailabilityService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import jewellery.inventory.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -21,7 +20,6 @@

@RestController
@RequestMapping("/users")
@CrossOrigin(origins = "${cors.origins}")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package jewellery.inventory.dto.request;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequestDto {
@NotBlank(message = "Email must not be blank, empty or null")
private String email;

@NotBlank(message = "Password must not be blank, empty or null")
private String password;
}
28 changes: 17 additions & 11 deletions src/main/java/jewellery/inventory/dto/request/UserRequestDto.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package jewellery.inventory.dto.request;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class UserRequestDto {
private static final String NAME_PATTERN_REGEX = "^(?!.*__)[A-Za-z0-9_]*$";
private static final String EMAIL_PATTERN_REGEX =
"^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";
private static final String NAME_SIZE_VALIDATION_MSG = "Size must be between 3 and 64";
private static final String NAME_PATTERN_VALIDATION_MSG =
"Name must only contain alphanumeric characters and underscores, and no consecutive underscores";
private static final String EMAIL_VALIDATION_MSG = "Email must be valid";
private static final String PWD_PATTERN_VALIDATION_MSG =
"Password must contain at least one digit, one lowercase letter, one uppercase letter, one special character, and be at least 8 characters long";

@NotEmpty
@Size(min = 3, max = 64, message = NAME_SIZE_VALIDATION_MSG)
@Pattern(regexp = NAME_PATTERN_REGEX, message = NAME_PATTERN_VALIDATION_MSG)
@NotBlank(message = "Name must not be blank, empty or null")
@Size(min = 3, max = 64, message = "Name size must be between 3 and 64")
@Pattern(regexp = "^(?!.*__)[A-Za-z0-9_]*$", message = NAME_PATTERN_VALIDATION_MSG)
private String name;

@NotEmpty
@Pattern(regexp = EMAIL_PATTERN_REGEX, message = EMAIL_VALIDATION_MSG)
@NotBlank(message = "Email must not be blank, empty or null")
@Pattern(
regexp = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$",
message = "Email must be valid")
private String email;

@NotBlank(message = "Password must not be blank, empty or null")
@Size(min = 8, message = "Size must be at least 8 characters")
@Pattern(
regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$",
message = PWD_PATTERN_VALIDATION_MSG)
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package jewellery.inventory.dto.response;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class UserAuthDetailsDto {
String token;
UserResponseDto user;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package jewellery.inventory.exception;

import io.jsonwebtoken.security.SignatureException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
Expand All @@ -10,8 +11,10 @@
import jewellery.inventory.exception.invalid_resource_quantity.InvalidResourceQuantityException;
import jewellery.inventory.exception.not_found.NotFoundException;
import jewellery.inventory.exception.not_found.ResourceInUserNotFoundException;
import jewellery.inventory.exception.security.InvalidSecretKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
Expand Down Expand Up @@ -46,6 +49,16 @@ public ResponseEntity<Object> handleBadDataExceptions(RuntimeException ex) {
return createErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage());
}

@ExceptionHandler({SignatureException.class, AuthenticationException.class})
public ResponseEntity<Object> handleAuthenticationException(AuthenticationException ex) {
return createErrorResponse(HttpStatus.UNAUTHORIZED, ex.getMessage());
}

@ExceptionHandler({ InvalidSecretKeyException.class })
public ResponseEntity<Object> handleBadSecretKey(RuntimeException ex) {
return createErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage());
}

private ResponseEntity<Object> createErrorResponse(HttpStatus status, Object error) {
Map<String, Object> body = new HashMap<>();
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_PATTERN));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ public class UserNotFoundException extends NotFoundException {
public UserNotFoundException(UUID id) {
super("User with id " + id + " was not found");
}

public UserNotFoundException(String email) {
super("User with email " + email + " was not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package jewellery.inventory.exception.security;

import jewellery.inventory.exception.security.jwt.JwtAuthenticationBaseException;


public class InvalidCredentialsException extends JwtAuthenticationBaseException {
public InvalidCredentialsException() {
super("Invalid credentials");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package jewellery.inventory.exception.security;

import io.jsonwebtoken.security.WeakKeyException;

public class InvalidSecretKeyException extends RuntimeException {
public InvalidSecretKeyException(WeakKeyException e) {
super("Invalid secret key: " + e.getMessage(), e.getCause());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package jewellery.inventory.exception.security.jwt;

import org.springframework.security.core.AuthenticationException;

public class JwtAuthenticationBaseException extends AuthenticationException {
public JwtAuthenticationBaseException(String msg) {
super(msg);
}

public JwtAuthenticationBaseException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package jewellery.inventory.exception.security.jwt;

public class JwtExpiredException extends JwtAuthenticationBaseException {

public JwtExpiredException() {
super("JWT token has expired");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package jewellery.inventory.exception.security.jwt;

public class JwtIsNotValidException extends JwtAuthenticationBaseException {
public JwtIsNotValidException() {
super("Invalid JWT token");
}

public JwtIsNotValidException(Exception e) {
super("Invalid JWT token: " + e.getMessage(), e.getCause());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package jewellery.inventory.exception.security.jwt;

public class JwtTokenNotFoundException extends JwtAuthenticationBaseException {
public JwtTokenNotFoundException() {
super("No JWT token found in request.");
}
}
6 changes: 6 additions & 0 deletions src/main/java/jewellery/inventory/model/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package jewellery.inventory.model;

public enum Role {
USER,
ADMIN
}
Loading