Skip to content

Commit

Permalink
Chore/authentication (#41)
Browse files Browse the repository at this point in the history
* added everything

* major changes.

* BE: Chore Backend User cleanups

* BE: Working Version of user registration

* Spotless code fixes

* Fix Swagger Security Configs

* Fix Spotless Pipeline issues

---------

Co-authored-by: Mgrdich <[email protected]>
  • Loading branch information
Yerish26 and Mgrdich authored May 10, 2024
1 parent 01efb2b commit c30e355
Show file tree
Hide file tree
Showing 33 changed files with 900 additions and 3 deletions.
23 changes: 23 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>

</dependencies>


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.llm_service.llm_service.config;

import com.llm_service.llm_service.persistance.entities.TokenEntity;
import com.llm_service.llm_service.persistance.repositories.token.TokenRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;

@Configuration
@RequiredArgsConstructor
public class CustomLogoutHandler implements LogoutHandler {
private final TokenRepository tokenRepository;

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String authHeader = request.getHeader("Authorization");

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return;
}

String token = authHeader.substring(7);
TokenEntity storedToken = tokenRepository.findByToken(token).orElse(null);

if (storedToken != null) {
storedToken.setLoggedOut(true);
tokenRepository.save(storedToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,67 @@
package com.llm_service.llm_service.config;

import com.llm_service.llm_service.persistance.entities.Role;
import com.llm_service.llm_service.service.jwt.filter.JwtAuthenticationFilter;
import com.llm_service.llm_service.service.user.UserDetailsServiceImp;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
private final UserDetailsServiceImp userDetailsServiceImp;

private final JwtAuthenticationFilter jwtAuthenticationFilter;

private final CustomLogoutHandler logoutHandler;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

// TODO fix the swagger security config
return http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(req -> req.anyRequest().permitAll())
.httpBasic(Customizer.withDefaults())
.authorizeHttpRequests(req -> req.requestMatchers(
"/login/**", "/register/**", "/forget-password/**", "/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.requestMatchers("/paid/**")
.hasAuthority(Role.PAID.name())
.anyRequest()
.authenticated())
.userDetailsService(userDetailsServiceImp)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(e -> e.accessDeniedHandler((request, response, accessDeniedException) ->
response.setStatus(HttpStatus.FORBIDDEN.value()))
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.logout(l -> l.logoutUrl("/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler(
(request, response, authentication) -> SecurityContextHolder.clearContext()))
.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ public ResponseEntity<List<DiscussionResponse>> continueConversation(
.body(discussions.stream().map(conversationApiMapper::map).toList());
}

@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Update a conversation",
content = {@Content(mediaType = "application/json")})
})
@Operation(summary = "update conversation title")
@PutMapping("/{id}")
public ResponseEntity<ConversationResponse> editConversation(
@PathVariable UUID id, @RequestBody ConversationTitleRequest conversationTitleRequest)
Expand All @@ -102,13 +110,29 @@ public ResponseEntity<ConversationResponse> editConversation(
return ResponseEntity.status(HttpStatus.OK).body(conversationApiMapper.map(conversation));
}

@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Deletes a conversation",
content = {@Content(mediaType = "application/json")})
})
@Operation(summary = "deletes a conversation")
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteConversation(@PathVariable UUID id) throws ConversationNotFoundException {
conversationService.getByID(id).orElseThrow(() -> new ConversationNotFoundException(id));
conversationService.delete(id);
return ResponseEntity.status(HttpStatus.OK).body(null);
}

@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Deletes all conversation",
content = {@Content(mediaType = "application/json")})
})
@Operation(summary = "deletes all conversations")
@DeleteMapping
public ResponseEntity<Void> deleteConversation() {
conversationService.deleteAll();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.llm_service.llm_service.controller.conversation;

import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class ConversationRequest {
@NonNull
String text;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.llm_service.llm_service.controller.conversation;

import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class ConversationTitleRequest {
@NonNull
String title;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.llm_service.llm_service.controller.user;

import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class LoginRequest {
@NonNull
String username;

@NonNull
String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.llm_service.llm_service.controller.user;

import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class LoginResponse {
String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.llm_service.llm_service.controller.user;

import com.llm_service.llm_service.dto.User;
import com.llm_service.llm_service.service.jwt.AuthenticationResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface UserApiMapper {
LoginResponse map(AuthenticationResponse response);

@Mapping(target = "name", source = "username")
UserResponse map(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.llm_service.llm_service.controller.user;

import com.llm_service.llm_service.dto.User;
import com.llm_service.llm_service.exception.user.UserAlreadyExistsException;
import com.llm_service.llm_service.exception.user.UserNotFoundException;
import com.llm_service.llm_service.exception.user.UsernameAlreadyExistsException;
import com.llm_service.llm_service.service.jwt.AuthenticationResponse;
import com.llm_service.llm_service.service.jwt.AuthenticationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*;

@CrossOrigin("http://localhost:4040")
@RestController
public class UserController {
private final AuthenticationService authenticationService;
private final UserApiMapper userApiMapper;

public UserController(AuthenticationService authenticationService, UserApiMapper userApiMapper) {
this.authenticationService = authenticationService;
this.userApiMapper = userApiMapper;
}

@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Creates a new user",
content = {@Content(mediaType = "application/json")})
})
@Operation(summary = "register the user")
@PostMapping("/register")
public ResponseEntity<UserResponse> register(@RequestBody UserRequest userRequest)
throws UsernameAlreadyExistsException {
User user = authenticationService.register(userRequest);
return ResponseEntity.status(HttpStatus.OK).body(userApiMapper.map(user));
}

@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Logs the user into the system",
content = {@Content(mediaType = "application/json")})
})
@Operation(summary = "login phase of the user")
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest)
throws UserNotFoundException, AuthenticationException {
AuthenticationResponse authenticationResponse = authenticationService.authenticate(loginRequest);
return ResponseEntity.ok(userApiMapper.map(authenticationResponse));
}

@ExceptionHandler(UserAlreadyExistsException.class)
ResponseEntity<String> handleUsernameAlreadyExistsExceptions(
UserAlreadyExistsException usernameAlreadyExistsException) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(usernameAlreadyExistsException.getMessage());
}

@ExceptionHandler(UsernameAlreadyExistsException.class)
ResponseEntity<String> handleUsernameAlreadyExistsExceptions(
UsernameAlreadyExistsException usernameAlreadyExistsException) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(usernameAlreadyExistsException.getMessage());
}

@ExceptionHandler(UserNotFoundException.class)
ResponseEntity<String> handleUsernameNotFoundExceptions(UserNotFoundException usernameNotFoundException) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(usernameNotFoundException.getMessage());
}

@ExceptionHandler(AuthenticationException.class)
ResponseEntity<String> handleAuthenticationException(AuthenticationException authenticationException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(authenticationException.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.llm_service.llm_service.controller.user;

import com.llm_service.llm_service.persistance.entities.Role;
import lombok.*;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class UserRequest {
@NonNull
String username;

@NonNull
String password;

@NonNull
String firstName;

@NonNull
String lastName;

@NonNull
Role role;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.llm_service.llm_service.controller.user;

import lombok.Value;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;

@Value
@Jacksonized
@SuperBuilder
public class UserResponse {
String name;
}
17 changes: 17 additions & 0 deletions backend/src/main/java/com/llm_service/llm_service/dto/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.llm_service.llm_service.dto;

import com.llm_service.llm_service.persistance.entities.Role;
import java.util.UUID;
import lombok.Builder;
import lombok.Value;

@Value
@Builder(toBuilder = true)
public class User {
UUID id;
String firstName;
String lastName;
String username;
String password;
Role role;
}
Loading

0 comments on commit c30e355

Please sign in to comment.