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
17 changes: 17 additions & 0 deletions services/user-service/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# JWT Configuration
# Copy this file to .env and update with your actual values

# JWT Secret Key (Base64 encoded, minimum 256 bits recommended)
# Generate a new one using: openssl rand -base64 64
JWT_SECRET=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970

# JWT Token Expiration Time (in milliseconds)
# 900000 = 15 minutes
# 3600000 = 1 hour
# 86400000 = 24 hours (current default)
JWT_EXPIRATION=86400000

# Database Configuration
DB_HOST=localhost
DB_USER=user
DB_PASSWORD=password
6 changes: 6 additions & 0 deletions services/user-service/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ build/

### VS Code ###
.vscode/

### Environment Variables ###
.env
.env.local
.env.production

Empty file.
40 changes: 36 additions & 4 deletions services/user-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,44 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.platformops.user_service.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "bearerAuth";
return new OpenAPI()
.info(new Info()
.title("User Service API")
.version("1.0")
.description("API for managing users in the platform. Use the 'Authorize' button to add your JWT token."))
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName,
new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("Enter your JWT token from /api/auth/login or /api/auth/register")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.platformops.user_service.config;

import com.platformops.user_service.filter.JwtAuthenticationFilter;
import com.platformops.user_service.service.CustomUserDetailsService;
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.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Autowired
private CustomUserDetailsService userDetailsService;

@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**", "/swagger-ui/**", "/v3/api-docs/**", "/actuator/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService);
return authProvider;
}

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

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.platformops.user_service.controller;

import com.platformops.user_service.dto.AuthResponse;
import com.platformops.user_service.dto.LoginRequest;
import com.platformops.user_service.dto.RegisterRequest;
import com.platformops.user_service.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
@Tag(name = "Authentication", description = "Endpoints for user authentication and registration")
@SecurityRequirement(name = "") // No authentication required for these endpoints
public class AuthController {

@Autowired
private AuthService authService;

@PostMapping("/register")
@Operation(
summary = "Register a new user",
description = "Create a new user account and receive a JWT token for authentication"
)
public ResponseEntity<AuthResponse> register(
@Parameter(description = "Registration details including name, email, and password", required = true)
@RequestBody RegisterRequest request) {
try {
AuthResponse response = authService.register(request);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}

@PostMapping("/login")
@Operation(
summary = "Login user",
description = "Authenticate a user with email and password, and receive a JWT token"
)
public ResponseEntity<AuthResponse> login(
@Parameter(description = "Login credentials including email and password", required = true)
@RequestBody LoginRequest request) {
try {
AuthResponse response = authService.login(request);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.platformops.user_service.controller;

import com.platformops.user_service.dto.UpdateUserRequest;
import com.platformops.user_service.model.User;
import com.platformops.user_service.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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 java.util.List;

@RestController
@RequestMapping("/api/users")
@Tag(name = "User Management", description = "Endpoints for managing user accounts and profiles")
@SecurityRequirement(name = "bearerAuth")
public class UserController {
@Autowired
private UserService userService;

@GetMapping
@Operation(
summary = "Get all users",
description = "Retrieve a paginated list of all registered users in the system. Useful for admin dashboards or user management interfaces."
)
public List<User> getAllUsers() {
return userService.getAllUsers();
}

@GetMapping("/{id}")
@Operation(
summary = "Get user by ID",
description = "Retrieve details of a specific user by their unique ID. Useful for displaying user profiles or editing user information."
)
public User getUserById(
@Parameter(description = "ID of the user to retrieve", example = "1")
@PathVariable Long id) {
return userService.getUserById(id);
}

@GetMapping("/me")
@Operation(
summary = "Get current user profile",
description = "Retrieve the profile of the currently authenticated user"
)
public ResponseEntity<?> getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String authenticatedEmail = authentication.getName();

User user = userService.getUserByEmail(authenticatedEmail);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("User not found");
}

return ResponseEntity.ok(user);
}

@PutMapping("/{id}")
@Operation(
summary = "Update a user",
description = "Update the details of the authenticated user. Users can only update their own profile."
)
public ResponseEntity<?> updateUser(
@Parameter(description = "ID of the user to update", example = "1")
@PathVariable Long id,
@Parameter(description = "User update request with name, email, and password", required = true)
@RequestBody UpdateUserRequest request) {

User updatedUser = userService.updateUser(id, request);
return ResponseEntity.ok(updatedUser);
}

@DeleteMapping("/{id}")
@Operation(
summary = "Delete a user",
description = "Remove the authenticated user's account from the system. Users can only delete their own account."
)
public ResponseEntity<?> deleteUser(
@Parameter(description = "ID of the user to delete", example = "1")
@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok("User deleted successfully");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.platformops.user_service.dto;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthResponse {
private String token;
@Builder.Default
private String type = "Bearer";
private Long id;
private String name;
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.platformops.user_service.dto;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
private String email;
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.platformops.user_service.dto;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {
private String name;
private String email;
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.platformops.user_service.dto;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UpdateUserRequest {
private String name;
private String email;
private String password;
}
Loading