From 12355f25c71899860b28324ca1546dcb78f97b01 Mon Sep 17 00:00:00 2001 From: Kshitij <160704796+kshitij-ka@users.noreply.github.com> Date: Thu, 3 Jul 2025 03:47:08 +0530 Subject: [PATCH] Refactor Auth and HDFS controllers, fix User model, and improve HDFS config - Rewrote AuthController to inject all dependencies via constructor - Fixed token refresh/login logic and added rate limiter and blacklist support - Implemented getters in LoginRequest DTO - Updated User model to implement UserDetails and extend entity.User - Switched HDFScontroller to use entity.User instead of models.User - Rewrote HDFSConfig to include static getHDFS() method and secure config via env vars - Simplified JwtService, added overload for entity.User, and fixed key handling --- .../skycrateBackend/config/HDFSConfig.java | 34 ++++-- .../controller/AuthController.java | 69 ++++++------ .../controller/HDFScontroller.java | 6 +- .../skycrateBackend/dto/LoginRequest.java | 7 +- .../backend/skycrateBackend/entity/User.java | 90 ++++++++++++++++ .../backend/skycrateBackend/models/User.java | 3 +- .../skycrateBackend/services/JwtService.java | 101 ++++++++---------- .../services/RateLimiterService.java | 23 ++++ 8 files changed, 225 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java create mode 100644 src/main/java/com/skycrate/backend/skycrateBackend/services/RateLimiterService.java diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java b/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java index 758e24b..56f6b99 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/config/HDFSConfig.java @@ -8,13 +8,35 @@ import org.springframework.context.annotation.Bean; import java.net.URI; import java.security.PrivilegedExceptionAction; - + // HDFS configuration bean to securely connect to a remote Hadoop cluster. +@Configuration public class HDFSConfig { - public static FileSystem getHDFS() throws Exception { - Configuration conf = new Configuration(); - conf.set("fs.defaultFS", "hdfs://namenode:9000"); - return FileSystem.get(new URI("hdfs://namenode:9000"), conf); + + private static final String HDFS_URI = System.getenv("HDFS_URI"); // export HDFS_URI=hdfs://192.168.29.30:9000 + private static final String HDFS_USER = System.getenv("HDFS_USER"); // Hadoop user (if needed) + + // Configures and returns a secured HDFS FileSystem instance. + @Bean + public FileSystem fileSystem() throws Exception { + return getHDFS(); // use the static method internally } + // Static method to get a FileSystem instance. Used by other classes like HDFSController. + public static FileSystem getHDFS() throws Exception { + if (HDFS_URI == null || HDFS_URI.isBlank()) { + throw new IllegalStateException("HDFS_URI environment variable not set."); + } -} + Configuration conf = new Configuration(); + conf.set("fs.defaultFS", HDFS_URI); + + if (HDFS_USER != null && !HDFS_USER.isBlank()) { + return UserGroupInformation.createRemoteUser(HDFS_USER) + .doAs((PrivilegedExceptionAction) () -> + FileSystem.get(new URI(HDFS_URI), conf) + ); + } else { + return FileSystem.get(new URI(HDFS_URI), conf); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java b/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java index f9f0321..33e0306 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java @@ -1,13 +1,17 @@ package com.skycrate.backend.skycrateBackend.controller; import com.skycrate.backend.skycrateBackend.dto.LoginRequest; -import com.skycrate.backend.skycrateBackend.services.JwtService; +import com.skycrate.backend.skycrateBackend.dto.LoginResponse; +import com.skycrate.backend.skycrateBackend.dto.TokenRefreshRequest; +import com.skycrate.backend.skycrateBackend.dto.TokenRefreshResponse; +import com.skycrate.backend.skycrateBackend.entity.RefreshToken; import com.skycrate.backend.skycrateBackend.entity.User; import com.skycrate.backend.skycrateBackend.repository.UserRepository; import com.skycrate.backend.skycrateBackend.security.TokenBlacklistService; +import com.skycrate.backend.skycrateBackend.services.JwtService; import com.skycrate.backend.skycrateBackend.services.RefreshTokenService; +import com.skycrate.backend.skycrateBackend.services.RateLimiterService; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -20,16 +24,26 @@ public class AuthController { private final AuthenticationManager authManager; private final JwtService jwtService; private final UserRepository userRepository; + private final RefreshTokenService refreshTokenService; + private final TokenBlacklistService tokenBlacklistService; + private final RateLimiterService rateLimiterService; - public AuthController(AuthenticationManager authManager, JwtService jwtService, UserRepository userRepository) { + public AuthController( + AuthenticationManager authManager, + JwtService jwtService, + UserRepository userRepository, + RefreshTokenService refreshTokenService, + TokenBlacklistService tokenBlacklistService, + RateLimiterService rateLimiterService + ) { this.authManager = authManager; this.jwtService = jwtService; this.userRepository = userRepository; + this.refreshTokenService = refreshTokenService; + this.tokenBlacklistService = tokenBlacklistService; + this.rateLimiterService = rateLimiterService; } - @Autowired - private RefreshTokenService refreshTokenService; - @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) { String ip = servletRequest.getRemoteAddr(); @@ -52,43 +66,29 @@ public class AuthController { rateLimiterService.resetAttempts(ip); - // ✅ Generate tokens String accessToken = jwtService.generateToken(user); RefreshToken refreshToken = refreshTokenService.createRefreshToken(user); return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken.getToken())); } - User user = userRepository.findByEmail(request.getEmail()) - .orElseThrow(() -> new RuntimeException("User not found")); + @PostMapping("/logout") + public ResponseEntity logout(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ResponseEntity.badRequest().body("Missing or invalid Authorization header"); + } - rateLimiterService.resetAttempts(ip); - String token = jwtService.generateToken(user); - return ResponseEntity.ok().body(token); + String token = authHeader.substring(7); + + tokenBlacklistService.blacklistToken(token); + + String email = jwtService.extractUsername(token); + userRepository.findByEmail(email).ifPresent(refreshTokenService::deleteByUser); + + return ResponseEntity.ok("Logged out successfully"); } - @Autowired - private TokenBlacklistService tokenBlacklistService; - -@PostMapping("/logout") -public ResponseEntity logout(HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return ResponseEntity.badRequest().body("Missing or invalid Authorization header"); - } - - String token = authHeader.substring(7); - - // Blacklist access token - tokenBlacklistService.blacklistToken(token); - - // Extract user from token and delete their refresh token - String email = jwtService.extractUsername(token); - userRepository.findByEmail(email).ifPresent(refreshTokenService::deleteByUser); - - return ResponseEntity.ok("Logged out successfully"); -} - @PostMapping("/refresh") public ResponseEntity refresh(@RequestBody TokenRefreshRequest request) { String requestToken = request.getRefreshToken(); @@ -105,5 +105,4 @@ public ResponseEntity logout(HttpServletRequest request) { }) .orElseGet(() -> ResponseEntity.status(403).body("Invalid refresh token")); } - } \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/controller/HDFScontroller.java b/src/main/java/com/skycrate/backend/skycrateBackend/controller/HDFScontroller.java index e4d0ea3..86d4ceb 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/controller/HDFScontroller.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/controller/HDFScontroller.java @@ -2,9 +2,8 @@ package com.skycrate.backend.skycrateBackend.controller; import com.skycrate.backend.skycrateBackend.config.HDFSConfig; import com.skycrate.backend.skycrateBackend.dto.ResponseDTO; -import com.skycrate.backend.skycrateBackend.models.User; +import com.skycrate.backend.skycrateBackend.entity.User; import com.skycrate.backend.skycrateBackend.repository.UserRepository; -import com.skycrate.backend.skycrateBackend.services.EncryptionUtil; import com.skycrate.backend.skycrateBackend.services.HDFSOperations; import com.skycrate.backend.skycrateBackend.utils.KeyUtil; import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil; @@ -36,9 +35,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.File; // For java.io.File -import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPrivateKeyForUser; -import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPublicKeyForUser; - @RestController @RequestMapping("/api/hdfs") public class HDFScontroller { diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/dto/LoginRequest.java b/src/main/java/com/skycrate/backend/skycrateBackend/dto/LoginRequest.java index edf52ae..e11b2af 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/dto/LoginRequest.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/dto/LoginRequest.java @@ -5,4 +5,9 @@ public class LoginRequest { private String password; // Getters and setters -} + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java b/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java new file mode 100644 index 0000000..25b4d47 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/entity/User.java @@ -0,0 +1,90 @@ +package com.skycrate.backend.skycrateBackend.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@Entity +@Table(name = "users") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User implements UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + private String fullname; + + @Lob + private byte[] publicKey; + + @Lob + private byte[] privateKey; + + // --- UserDetails interface methods --- + @Override + public Collection getAuthorities() { + return List.of(); // Add roles/authorities if needed + } + + @Override + public String getUsername() { + return email; // or return username if that's your login key + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + // --- Extra getter methods for HDFScontroller compatibility --- + public byte[] getPublicKey() { + return publicKey; + } + + public byte[] getPrivateKey() { + return privateKey; + } + + public String getFullname() { + return fullname; + } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/models/User.java b/src/main/java/com/skycrate/backend/skycrateBackend/models/User.java index c54c66b..a07b452 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/models/User.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/models/User.java @@ -1,6 +1,5 @@ package com.skycrate.backend.skycrateBackend.models; -import java.time.LocalDateTime; import java.util.Collection; import java.util.Date; import java.util.List; @@ -13,7 +12,7 @@ import jakarta.persistence.*; @Table(name = "users") @Entity -public class User implements UserDetails { +public class User extends com.skycrate.backend.skycrateBackend.entity.User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java index 19db5e9..fd644ed 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/JwtService.java @@ -1,83 +1,66 @@ package com.skycrate.backend.skycrateBackend.services; -import java.security.Key; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; - +import com.skycrate.backend.skycrateBackend.entity.User; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.security.Key; +import java.util.Date; +import java.util.function.Function; @Service public class JwtService { - @Value("${security.jwt.secret-key}") - private String secretKey; - - @Value("${security.jwt.expiration-time}") - private long jwtExpiration; - public String extractUsername(String token){ - return extractClaim(token,Claims::getSubject); + // Recommend moving this key to environment variable or config file + private static final String SECRET_KEY = "your-256-bit-secret-your-256-bit-secret"; // must be 256-bit + + private final long EXPIRATION_TIME = 1000 * 60 * 15; // 15 minutes + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); } - public T extractClaim(String token,Function claimsResolver){ - final Claims claims=extractAllClaims(token); - return claimsResolver.apply(claims); - + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); } - public String generateToken(UserDetails userDetails) { - return generateToken(new HashMap<>(), userDetails); - } - - public String generateToken(Map extraClaims, UserDetails userDetails) { - return buildToken(extraClaims, userDetails, jwtExpiration); - } - - public long getExpirtationTime(){ - return jwtExpiration; - } - private String buildToken(Map extraClaims,UserDetails userDetails,long expiration){ - - return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername()) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(getSignInKey(), SignatureAlgorithm.HS256) - .compact(); - } - - public boolean isTokenValid(String token, UserDetails userDetails) { - final String username = extractUsername(token); - return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); - } - - private boolean isTokenExpired(String token) { - return extractExpiration(token).before(new Date()); - } - - private Date extractExpiration(String token) { + public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } - private Claims extractAllClaims(String token) { - return Jwts - .parserBuilder() - .setSigningKey(getSignInKey()) + public T extractClaim(String token, Function claimsResolver) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); + return claimsResolver.apply(claims); } - private Key getSignInKey() { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - return Keys.hmacShaKeyFor(keyBytes); + public boolean isTokenValid(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } + + public boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + public String generateToken(UserDetails userDetails) { + return Jwts.builder() + .setSubject(userDetails.getUsername()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + // Overload for your entity + public String generateToken(User user) { + return generateToken((UserDetails) user); } } diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/RateLimiterService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/RateLimiterService.java new file mode 100644 index 0000000..47f6ee4 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/RateLimiterService.java @@ -0,0 +1,23 @@ +// NEED TO IMPLEMENT SAHI SE +package com.skycrate.backend.skycrateBackend.services; + +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class RateLimiterService { + private final ConcurrentHashMap attempts = new ConcurrentHashMap<>(); + + public boolean isBlocked(String ip) { + return attempts.getOrDefault(ip, 0) >= 5; + } + + public void recordFailedAttempt(String ip) { + attempts.put(ip, attempts.getOrDefault(ip, 0) + 1); + } + + public void resetAttempts(String ip) { + attempts.remove(ip); + } +} \ No newline at end of file