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
1,120 changes: 0 additions & 1,120 deletions logs/app.log

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/admin")
Expand Down Expand Up @@ -45,7 +46,7 @@ public List<User> getUsers() {
description = "User activation status updated successfully")
public void updateUserActivation(
@Parameter(description = "ID of the user to activate/deactivate")
@PathVariable final Long userId,
@PathVariable final UUID userId,

@Parameter(description = "Set to true to activate,"
+ " false to deactivate the user")
Expand All @@ -65,7 +66,7 @@ public void updateUserActivation(
description = "User deleted successfully")
public void deleteUser(
@Parameter(description = "ID of the user to delete")
@PathVariable final Long userId) {
@PathVariable final UUID userId) {

LOGGER.debug("Admin requested to delete user with userId={}", userId);
adminService.deleteUser(userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.UUID;

@RestController
@RequestMapping("/user")
public class UserController {
Expand All @@ -29,7 +32,7 @@ public UserController(final UserService userService) {
description = "Allows user to update their name.")
@ApiResponse(responseCode = "200",
description = "User profile updated successfully")
public void updateProfile(@PathVariable final Long userId,
public void updateProfile(@PathVariable final UUID userId,
@Valid @RequestBody final String name) {
LOGGER.debug("Received updateProfile request for userId={}", userId);
userService.updateUserProfile(userId, name);
Expand Down
62 changes: 7 additions & 55 deletions src/main/java/com/podzilla/auth/dto/CustomUserDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

@JsonIgnoreProperties(ignoreUnknown = true)
@Builder
@Getter
@NoArgsConstructor(force = true)
@AllArgsConstructor
public class CustomUserDetails implements UserDetails {

private String username;
Expand All @@ -21,65 +27,11 @@ public class CustomUserDetails implements UserDetails {
@JsonDeserialize(contentAs = CustomGrantedAuthority.class)
private Set<GrantedAuthority> authorities;

@Getter
private final boolean accountNonExpired;
@Getter
private final boolean accountNonLocked;
@Getter
private final boolean credentialsNonExpired;
@Getter
private final boolean enabled;

public CustomUserDetails() {
// No-arg constructor required by Jackson
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}

public CustomUserDetails(final String username, final String password,
final Set<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}

public CustomUserDetails(final String username,
final String password,
final boolean enabled,
final boolean accountNonExpired,
final boolean accountNonLocked,
final boolean credentialsNonExpired,
final Set<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
}

@Override
public String getUsername() {
return username;
}

@Override
public String getPassword() {
return password;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

public void eraseCredentials() {
this.password = null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/podzilla/auth/model/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.podzilla.auth.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.Id;
Expand Down Expand Up @@ -35,6 +36,7 @@ public class RefreshToken {
@Column(updatable = false, nullable = false)
private UUID id;

@JsonIgnore
@ManyToOne(optional = false)
@JoinColumn(name = "user_id")
private User user;
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/com/podzilla/auth/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,27 @@

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Getter;

@Entity
@Table(name = "users")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

@NotBlank(message = "Name is required")
private String name;
Expand All @@ -45,16 +48,19 @@ public class User {
@NotBlank(message = "Password is required")
private String password;

@Builder.Default
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();

@Builder.Default
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL,
orphanRemoval = true)
private Set<RefreshToken> refreshTokens = new HashSet<>();

@Builder.Default
@Column(columnDefinition = "BOOLEAN DEFAULT TRUE")
private Boolean enabled = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
public interface RefreshTokenRepository extends
JpaRepository<RefreshToken, UUID> {
Optional<RefreshToken> findByIdAndExpiresAtAfter(UUID id, Instant date);
Optional<RefreshToken> findByUserIdAndExpiresAtAfter(Long userId,
Optional<RefreshToken> findByUserIdAndExpiresAtAfter(UUID userId,
Instant date);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByEmail(String email);
Boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.podzilla.auth.security;

import com.podzilla.auth.service.CustomUserDetailsService;
import com.podzilla.auth.service.CacheService;
import com.podzilla.auth.service.TokenService;
import io.micrometer.common.lang.NonNullApi;
import jakarta.servlet.FilterChain;
Expand All @@ -23,13 +23,13 @@
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private final TokenService tokenService;
private final CustomUserDetailsService customUserDetailsService;
private final CacheService cacheService;

public JWTAuthenticationFilter(
final TokenService tokenService,
final CustomUserDetailsService customUserDetailsService) {
final CacheService cacheService) {
this.tokenService = tokenService;
this.customUserDetailsService = customUserDetailsService;
this.cacheService = cacheService;
}

private static final Logger LOGGER =
Expand All @@ -47,8 +47,7 @@ protected void doFilterInternal(final HttpServletRequest request,
String userEmail = tokenService.extractEmail();

UserDetails userDetails =
customUserDetailsService
.loadUserByUsernameCached(userEmail);
cacheService.loadUserByUsername(userEmail);

UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/com/podzilla/auth/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
Expand Down Expand Up @@ -36,11 +37,14 @@ SecurityFilterChain securityFilterChain(final HttpSecurity http)
UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests((auth) ->
auth
.requestMatchers("/auth/login")
.requestMatchers(HttpMethod.POST,
"/auth/login")
.permitAll()
.requestMatchers("/auth/register")
.requestMatchers(HttpMethod.POST,
"/auth/register")
.permitAll()
.requestMatchers("/auth/refresh-token")
.requestMatchers(HttpMethod.POST,
"/auth/refresh-token")
.permitAll()
.requestMatchers("/admin/**")
.hasAuthority("ROLE_ADMIN")
Expand Down
37 changes: 34 additions & 3 deletions src/main/java/com/podzilla/auth/service/AdminService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.podzilla.auth.service;

import com.podzilla.auth.dto.CustomGrantedAuthority;
import com.podzilla.auth.dto.CustomUserDetails;
import com.podzilla.auth.model.User;
import com.podzilla.auth.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class AdminService {
Expand All @@ -17,35 +24,59 @@ public class AdminService {

private final UserRepository userRepository;
private final UserService userService;
private final CacheService cacheService;

public AdminService(final UserRepository userRepository,
final UserService userService) {
final UserService userService,
final CacheService cacheService) {
this.userRepository = userRepository;
this.userService = userService;
this.cacheService = cacheService;
}

public List<User> getUsers() {
return userRepository.findAll();
}

@Transactional
public void updateUserActivation(final Long userId,
public void updateUserActivation(final UUID userId,
final boolean isActive) {
User user = userService.getUserOrThrow(userId);
LOGGER.debug("Updating isActive status for userId={} "
+ "from {} to {}", userId, user.getEnabled(), isActive);
user.setEnabled(isActive);
cacheService.updateUserDetailsCache(user);
userRepository.save(user);
LOGGER.debug("User activation status updated "
+ "successfully for userId={}", userId);
}


@Transactional
public void deleteUser(final Long userId) {
public void deleteUser(final UUID userId) {
User user = userService.getUserOrThrow(userId);
LOGGER.debug("Deleting user with userId={}", userId);
cacheService.evictUserDetailsCache(user);
userRepository.delete(user);
LOGGER.debug("User deleted successfully with userId={}", userId);
}

public static UserDetails getUserDetails(final User user) {
Set<GrantedAuthority> authorities = user
.getRoles()
.stream()
.map((role) -> new CustomGrantedAuthority(
role.getErole().name()))
.collect(Collectors.toSet());

return CustomUserDetails.builder()
.username(user.getEmail())
.password(user.getPassword())
.enabled(user.getEnabled())
.authorities(authorities)
.accountNonExpired(true)
.credentialsNonExpired(true)
.accountNonLocked(true)
.build();
}
}
Loading