Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
- name: Set up JDK 23
uses: actions/setup-java@v4
with:
java-version: '21'
java-version: '23'
distribution: 'temurin'

- name: Grant execute permission for gradlew
Expand Down
39 changes: 38 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
# Gradle
.gradle/
build/
Expand All @@ -17,4 +54,4 @@ out/
# Env
.env

src/main/resources/application-dev.properties
src/main/resources/application-dev.properties
27 changes: 18 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.8'
id 'org.springframework.boot' version '4.0.3'
id 'io.spring.dependency-management' version '1.1.7'
}

Expand All @@ -10,7 +10,7 @@ description = 'Backend'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(23)
}
}

Expand All @@ -19,18 +19,27 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

developmentOnly 'org.springframework.boot:spring-boot-devtools'

runtimeOnly 'org.postgresql:postgresql'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
// implementation 'org.springframework.boot:spring-boot-starter-web'
// testImplementation 'org.springframework.boot:spring-boot-starter-restclient-test'
// developmentOnly 'org.springframework.boot:spring-boot-devtools'

}

tasks.named('test') {
useJUnitPlatform()
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/codzilla/backend/ExampleEndpoint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codzilla.backend;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class ExampleEndpoint {
@GetMapping("/endpoint")
public ResponseEntity<Map<String, String>> endpoint() {
return ResponseEntity.ok(Map.of("message", "info"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.codzilla.backend.auth;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class AdminAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {

response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"You don't have rights to access this page.\"}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.codzilla.backend.auth.AdminController;

import com.codzilla.backend.auth.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin")
@PreAuthorize("hasAuthority('ADMIN')")
public class AdminController {

private final UserService userService;

public AdminController(UserService userService) {
this.userService = userService;
}

@GetMapping("/info")
String getAdminInfo() {
return "Some admin info.";
}

@GetMapping("/users")
ResponseEntity<?> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.codzilla.backend.auth.AuthController;

import com.codzilla.backend.auth.JWTUtils.JWTUtils;
import com.codzilla.backend.auth.User;
import com.codzilla.backend.auth.UserService;
import com.codzilla.backend.auth.config.Settings;
import com.codzilla.backend.auth.dto.LoginRequestDTO;
import com.codzilla.backend.auth.dto.LoginResponseDTO;
import com.codzilla.backend.auth.dto.RegisterRequestDTO;
import com.codzilla.backend.auth.dto.RegisterResponseDTO;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthenticationManager authManager;
private final JWTUtils jwtUtils;
private final Settings settings;
private final UserService userService;

@Autowired
public AuthController(AuthenticationManager authManager,
JWTUtils jwtUtils, Settings settings, UserService userService) {
this.userService = userService;
this.authManager = authManager;
this.jwtUtils = jwtUtils;
this.settings = settings;
}

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequestDTO request, HttpServletResponse response) {
log.info("Auth user by password...");
log.info(request.email() + request.rawPassword());
Authentication auth = authManager.authenticate(
new UsernamePasswordAuthenticationToken(request.email(), request.rawPassword())
);

var accessToken = jwtUtils.generateAccessToken(auth);
Cookie jwtCookie = new Cookie("jwt", accessToken);
jwtCookie.setHttpOnly(true);
jwtCookie.setSecure(false);
jwtCookie.setPath("/");
jwtCookie.setMaxAge((int) settings.getRefreshTokenTtl().toSeconds());
response.addCookie(jwtCookie);

var refreshToken = jwtUtils.generateRefreshToken(auth);
Cookie refreshCookie = new Cookie("refresh_jwt", refreshToken);
refreshCookie.setPath("/");
refreshCookie.setHttpOnly(true);
refreshCookie.setMaxAge((int) settings.getRefreshTokenTtl().toSeconds());
refreshCookie.setSecure(false);
response.addCookie(refreshCookie);

User user = userService.getByEmail(request.email());
return ResponseEntity.ok(new LoginResponseDTO(user.getNickname()));
}

@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletResponse response) {
Cookie cookie = new Cookie("jwt", null);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(0);

response.addCookie(cookie);

return ResponseEntity.ok("Logged out successfully");
}


@PostMapping("/signup")
public ResponseEntity<?> signUp(@RequestBody RegisterRequestDTO request) {
userService.registerUser(request);
return ResponseEntity.ok(new RegisterResponseDTO(request.nickname()));
}

@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(HttpServletRequest request, HttpServletResponse response) {
String refreshToken = null;
if (request.getCookies() != null) {
for (var cookie : request.getCookies()) {
if ("refresh_jwt".equals(cookie.getName())) {
refreshToken = cookie.getValue();
break;
}
}

if (refreshToken == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No token in cookie.");
if (!jwtUtils.validateToken(refreshToken))
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token is wrong.");

String email = jwtUtils.getEmailFromToken(refreshToken);

User user = userService.getByEmail(email);

UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities()
);

var accessToken = jwtUtils.generateAccessToken(auth);
Cookie cookie = new Cookie("jwt", accessToken);
cookie.setHttpOnly(true);
cookie.setSecure(false);
cookie.setPath("/");
cookie.setMaxAge((int) settings.getRefreshTokenTtl().toSeconds());
response.addCookie(cookie);
return ResponseEntity.ok("Jwt access was updated.");

}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No cookie here.");
}

@PostMapping("/create-admin")
void createAdmin() {
userService.createAdmin();
}

}
32 changes: 32 additions & 0 deletions src/main/java/com/codzilla/backend/auth/AuthExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.codzilla.backend.auth;

import com.codzilla.backend.auth.Exceptions.RestException;
import com.codzilla.backend.auth.dto.ErrorResponseDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import tools.jackson.databind.ObjectMapper;

@RestControllerAdvice
public class AuthExceptionHandler {
@Autowired
ObjectMapper objectMapper;

@ExceptionHandler(RestException.class)
public ResponseEntity<String> handleUserNotFound(RestException exception) {
ErrorResponseDTO dto = new ErrorResponseDTO(exception);
return new ResponseEntity<>(objectMapper.writeValueAsString(dto), exception.getStatus());
}

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<String> handleWrongCredentials(BadCredentialsException exception){
ErrorResponseDTO dto = new ErrorResponseDTO(
"Wrong email or password",
HttpStatus.UNAUTHORIZED
);
return new ResponseEntity<>(objectMapper.writeValueAsString(dto), HttpStatus.UNAUTHORIZED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.codzilla.backend.auth.Exceptions;

import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
public class RestException extends RuntimeException {
HttpStatus status;

RestException(HttpStatus status, String message) {
super(message);
this.status = status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.codzilla.backend.auth.Exceptions;

import org.springframework.http.HttpStatus;

public class UserAlreadyExistsException extends RestException {
public UserAlreadyExistsException() {
super(HttpStatus.CONFLICT, "User already exists");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.codzilla.backend.auth.Exceptions;

import org.springframework.http.HttpStatus;

public class UserNotFoundException extends RestException {
public UserNotFoundException() {
super(HttpStatus.CONFLICT, "User not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.codzilla.backend.auth.Exceptions;

import org.springframework.http.HttpStatus;

public class UsernameIsTakenException extends RestException {
public UsernameIsTakenException() {
super(HttpStatus.CONFLICT, "Username is taken");
}
}
Loading
Loading