From 91e38fad45c39d98e7ec36002644c23567b7fa02 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 12:02:17 -0600 Subject: [PATCH] comment security and config --- .../backend/config/ActivityLoggingFilter.java | 23 ++++++++++++ ...tivityLoggingFilterRegistrationConfig.java | 6 +++ .../config/ApplicationStartupListener.java | 6 +++ .../backend/config/BusinessProperties.java | 7 ++++ .../petshop/backend/config/CacheConfig.java | 7 ++++ .../backend/config/DataInitializer.java | 8 ++++ .../config/FlywayContextInitializer.java | 7 ++++ .../config/LocalCatalogSeedInitializer.java | 7 ++++ .../config/TomcatPathToleranceConfig.java | 7 ++++ .../TrailingSlashNormalizationFilter.java | 14 +++++++ .../WebSocketAuthChannelInterceptor.java | 37 +++++++++++++++++++ .../backend/config/WebSocketConfig.java | 7 ++++ .../backend/security/AppPrincipal.java | 7 ++++ .../security/JwtAuthenticationFilter.java | 19 ++++++++++ .../com/petshop/backend/security/JwtUtil.java | 25 +++++++++++++ .../backend/security/RateLimitFilter.java | 7 ++++ .../backend/security/RateLimiterService.java | 17 +++++++++ .../security/RestAccessDeniedHandler.java | 7 ++++ .../RestAuthenticationEntryPoint.java | 6 +++ .../backend/security/SecurityConfig.java | 7 ++++ .../security/UserAuthCacheService.java | 7 ++++ .../security/UserDetailsServiceImpl.java | 6 +++ 22 files changed, 244 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java index 0c30cece..ef6749da 100644 --- a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java +++ b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java @@ -1,3 +1,11 @@ +/* + * Logs write operations (POST, PUT, DELETE) to the activity log. + * Skips GET requests and internal endpoints like health checks. + * Builds a human-readable description for each action. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; @@ -77,11 +85,24 @@ public class ActivityLoggingFilter extends OncePerRequestFilter { activityLogService.record(userId, activity); } + /** + * Turns a request method + URL + status into a human-readable description + * like "Created a new product" or "Failed login attempt". + * Parses the URL as /api/v1/{resource}/{id?}/{sub?}/{subsub?} and matches + * against known patterns. + * @param method HTTP method (GET, POST, etc.) + * @param rawUri the request URI, possibly with query params + * @param status the HTTP response status code + * @return a readable description, or null if we don't recognize the pattern + */ private String describe(String method, String rawUri, int status) { + // Strip query params so we only deal with the path String uri = rawUri.contains("?") ? rawUri.substring(0, rawUri.indexOf('?')) : rawUri; + // Split into segments: [api, v1, resource, id/action, sub, ...] String[] parts = uri.replaceFirst("^/+", "").split("/"); if (parts.length < 3) return null; + // parts[2] is the resource name (e.g. "products", "auth", "cart") String r = parts[2]; String seg3 = parts.length > 3 ? parts[3] : null; String seg4 = parts.length > 4 ? parts[4] : null; @@ -89,6 +110,8 @@ public class ActivityLoggingFilter extends OncePerRequestFilter { boolean seg3IsId = seg3 != null && seg3.matches("\\d+"); boolean seg4IsId = seg4 != null && seg4.matches("\\d+"); + // Normalize: if seg3 is a numeric ID, treat seg4 as the sub-action + // otherwise seg3 itself is the sub-action (like "login", "add", etc.) String id = seg3IsId ? seg3 : null; String sub = seg3IsId ? seg4 : seg3; String subsub = seg3IsId ? seg5 : seg4; diff --git a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java index 66427661..b57f574d 100644 --- a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java @@ -1,3 +1,9 @@ +/* + * Registers the activity logging filter with the servlet container. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; diff --git a/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java b/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java index 24ad9a6a..f937ad82 100644 --- a/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java +++ b/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java @@ -1,3 +1,9 @@ +/* + * Runs once on startup to mark past appointments as completed. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.service.AppointmentService; diff --git a/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java index 6d1cee18..d154fe7e 100644 --- a/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java +++ b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java @@ -1,3 +1,10 @@ +/* + * Business settings loaded from application.yml (store hours, + * slot intervals, image size limits, discount/loyalty config). + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/backend/src/main/java/com/petshop/backend/config/CacheConfig.java b/backend/src/main/java/com/petshop/backend/config/CacheConfig.java index 4a8bc6b0..839002ca 100644 --- a/backend/src/main/java/com/petshop/backend/config/CacheConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/CacheConfig.java @@ -1,3 +1,10 @@ +/* + * Sets up Caffeine caching for the app. Currently used to cache + * user auth lookups so the filter doesn't query the DB every time. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.github.benmanes.caffeine.cache.Caffeine; diff --git a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java index 89cbb18e..56a26deb 100644 --- a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -1,3 +1,11 @@ +/* + * Creates default admin, staff, and customer accounts on startup + * if they don't already exist. Also fills in missing fields on + * existing accounts to keep them consistent. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java index 15c9b976..94d05d32 100644 --- a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java @@ -1,3 +1,10 @@ +/* + * Runs Flyway migrations before the app starts. Retries up to + * 15 times so the app can wait for the database container to be ready. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.flywaydb.core.Flyway; diff --git a/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java b/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java index 18e64f05..74b6bfff 100644 --- a/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java @@ -1,3 +1,10 @@ +/* + * Adds extra pet and product seed data in the local dev profile + * if the database only has the base seed rows. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.repository.PetRepository; diff --git a/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java b/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java index 9a89c5ab..7b6b3d7e 100644 --- a/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java @@ -1,3 +1,10 @@ +/* + * Tells Tomcat to accept backslashes in URLs so the Android + * and desktop clients don't get rejected for path issues. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; diff --git a/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java b/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java index 38ececb9..f915ec4f 100644 --- a/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java +++ b/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java @@ -1,3 +1,10 @@ +/* + * Strips trailing slashes and normalizes paths so /api/pets/ + * and /api/pets both hit the same controller. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import jakarta.servlet.FilterChain; @@ -63,6 +70,12 @@ public class TrailingSlashNormalizationFilter extends OncePerRequestFilter { filterChain.doFilter(wrapper, response); } + /** + * Cleans up a URL path by fixing backslashes, collapsing double slashes, + * lowercasing API/WS paths, and stripping trailing slashes. + * @param value the raw path string + * @return the normalized path, or null if input was null + */ private String normalizePath(String value) { if (value == null) { return null; @@ -74,6 +87,7 @@ public class TrailingSlashNormalizationFilter extends OncePerRequestFilter { if (shouldLowercase(normalized)) { normalized = normalized.toLowerCase(java.util.Locale.ROOT); } + // Strip trailing slashes but keep the root "/" intact int end = normalized.length(); while (end > 1 && normalized.charAt(end - 1) == '/') { end--; diff --git a/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java index c7f23fc4..0b55b216 100644 --- a/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java +++ b/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -1,3 +1,11 @@ +/* + * Intercepts WebSocket messages to authenticate and authorize users. + * Validates the token on CONNECT, then checks permissions on + * SUBSCRIBE and SEND for chat topics. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; @@ -33,6 +41,14 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { this.chatService = chatService; } + /** + * Intercepts every STOMP message before it's sent. On CONNECT it validates the + * JWT and stores the authenticated user. On SUBSCRIBE/SEND it checks that the + * user has access to the requested chat destination. + * @param message the STOMP message being sent + * @param channel the channel the message is going through + * @return the original message if everything checks out + */ @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); @@ -95,6 +111,14 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return message; } + /** + * Tries to figure out who the user is from multiple sources: the principal on the + * message, the session attributes (fallback for some STOMP clients), or by + * re-parsing the token from headers as a last resort. + * @param principal the principal attached to the message, may be null + * @param accessor the STOMP header accessor for fallback lookups + * @return the User entity, or null if unauthenticated + */ private User resolveUser(Principal principal, StompHeaderAccessor accessor) { Principal currentPrincipal = principal; if (currentPrincipal == null && accessor.getSessionAttributes() != null) { @@ -136,6 +160,13 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return user; } + /** + * Checks if the user is allowed to subscribe to this destination. + * Customers can only subscribe to their own conversations, not the + * staff-wide conversation feed. + * @param destination the STOMP destination being subscribed to + * @param user the user trying to subscribe + */ private void authorizeSubscription(String destination, User user) { destination = normalizeDestination(destination); if (destination == null || destination.startsWith("/user/queue/")) { @@ -157,6 +188,12 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { throw new IllegalArgumentException("Not authorized to subscribe to destination"); } + /** + * Checks if the user is allowed to send a message to this destination. + * Only allows sending to conversation message endpoints that the user has access to. + * @param destination the STOMP destination being sent to + * @param user the user trying to send + */ private void authorizeSend(String destination, User user) { destination = normalizeDestination(destination); Long conversationId = extractConversationId(destination, "/app/chat/conversations/"); diff --git a/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java index 67dc1048..4ff58981 100644 --- a/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java @@ -1,3 +1,10 @@ +/* + * WebSocket configuration for the live chat feature. + * Registers STOMP endpoints and the auth interceptor. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.context.annotation.Configuration; diff --git a/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java b/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java index 30ceca66..f1a1c8b5 100644 --- a/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java +++ b/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java @@ -1,3 +1,10 @@ +/* + * Represents the currently logged-in user in the security context. + * Holds the user ID, username, role, and token version. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index db7d47fc..52823e98 100644 --- a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -1,3 +1,10 @@ +/* + * Filter that runs on every request to validate the JWT token + * and set up the security context if the token is valid. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; @@ -30,12 +37,20 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { this.apiErrorResponder = apiErrorResponder; } + /** + * Extracts the JWT from the Authorization header, validates it, and sets up + * the Spring Security context so downstream filters/controllers know who the user is. + * @param request the incoming HTTP request + * @param response the HTTP response (used to write 401 errors) + * @param filterChain the remaining filters to invoke + */ @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { + // Skip requests that don't have a Bearer token final String authHeader = request.getHeader("Authorization"); final String jwt; if (authHeader == null || !authHeader.startsWith("Bearer ")) { @@ -43,6 +58,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Strip "Bearer " prefix to get the raw token jwt = authHeader.substring(7); Long userId; String username; @@ -58,6 +74,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Only set up auth context if there isn't one already for this request if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { if (jwtUtil.extractExpiration(jwt).before(new Date())) { writeUnauthorized(request, response, "Invalid or expired token", null); @@ -69,6 +86,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { writeUnauthorized(request, response, "User account is inactive", null); return; } + // Token version mismatch means the user logged out or changed password if (!authData.tokenVersion().equals(jwtTokenVersion)) { writeUnauthorized(request, response, "Invalid or expired token", null); return; @@ -82,6 +100,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Build the authentication object and store it in the security context AppPrincipal principal = new AppPrincipal(userId, username, role, jwtTokenVersion); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( principal, diff --git a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java index b0a5fec5..fc4c0a4f 100644 --- a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java +++ b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java @@ -1,3 +1,10 @@ +/* + * JWT token utility for generating, parsing, and validating tokens. + * Tokens store userId, username, role, and a version for invalidation. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; @@ -50,6 +57,12 @@ public class JwtUtil { return extractAllClaims(token).get("role", String.class); } + /** + * Gets the token version from the JWT, used to invalidate old tokens + * when the user logs out or changes their password. + * @param token the JWT string + * @return the token version number, or null if not present + */ public Integer extractTokenVersion(String token) { Number tokenVersion = extractAllClaims(token).get("tokenVersion", Number.class); return tokenVersion == null ? null : tokenVersion.intValue(); @@ -76,6 +89,11 @@ public class JwtUtil { return extractExpiration(token).before(new Date()); } + /** + * Creates a new JWT containing the user's id, username, role, and token version. + * @param user the user to generate a token for + * @return the signed JWT string + */ public String generateToken(User user) { Map claims = new HashMap<>(); claims.put("username", user.getUsername()); @@ -94,6 +112,13 @@ public class JwtUtil { .compact(); } + /** + * Checks that the token belongs to this user, has the right role and version, + * and hasn't expired yet. + * @param token the JWT string + * @param user the user to validate against + * @return true if the token is valid for this user + */ public Boolean validateToken(String token, User user) { Long userId = extractUserId(token); String role = extractRole(token); diff --git a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java index 31f2434f..fd060f1c 100644 --- a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java +++ b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java @@ -1,3 +1,10 @@ +/* + * Filter that applies rate limiting to auth endpoints. + * Each rule defines max requests and window size in minutes. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java b/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java index 4f6eb94f..a95bafd0 100644 --- a/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java +++ b/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java @@ -1,3 +1,10 @@ +/* + * Sliding-window rate limiter that tracks request timestamps per key. + * Used to limit login/register attempts by IP. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import org.springframework.scheduling.annotation.Scheduled; @@ -15,12 +22,22 @@ public class RateLimiterService { private final Map> buckets = new ConcurrentHashMap<>(); + /** + * Checks whether a request identified by key is within the rate limit. + * Uses a sliding window: keeps a queue of timestamps per key, drops any + * that fall outside the window, then checks if there's room for one more. + * @param key identifier for the rate limit bucket (e.g. IP address) + * @param maxRequests max number of requests allowed in the window + * @param window the time window to count requests in + * @return true if the request is allowed, false if rate limited + */ public boolean isAllowed(String key, int maxRequests, Duration window) { Instant now = Instant.now(); Instant windowStart = now.minus(window); Deque timestamps = buckets.computeIfAbsent(key, k -> new ArrayDeque<>()); synchronized (timestamps) { + // Remove timestamps that are older than the window while (!timestamps.isEmpty() && timestamps.peekFirst().isBefore(windowStart)) { timestamps.pollFirst(); } diff --git a/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java b/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java index fcaa49bc..dc41867b 100644 --- a/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java +++ b/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java @@ -1,3 +1,10 @@ +/* + * Returns a JSON error when a logged-in user tries to access + * something they don't have permission for. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java b/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java index fb28314b..ba95590f 100644 --- a/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java +++ b/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java @@ -1,3 +1,9 @@ +/* + * Returns a JSON error when an unauthenticated request hits a protected endpoint. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java index f154ead1..4d83867c 100644 --- a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -1,3 +1,10 @@ +/* + * Security setup for the app. Defines which endpoints are public, + * which need login, and configures the filter chain. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java b/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java index 69d39a9d..c67d7da9 100644 --- a/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java +++ b/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java @@ -1,3 +1,10 @@ +/* + * Caches user auth data (active status, token version) so the + * JWT filter doesn't hit the database on every request. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.repository.UserRepository; diff --git a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index f6956615..87b6463d 100644 --- a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -1,3 +1,9 @@ +/* + * Loads user details from the database for Spring Security login. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User;