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 82f5b48..2982181 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java @@ -66,4 +66,25 @@ public class AuthController { return ResponseEntity.ok("Logged out successfully"); } + + @Autowired + private RefreshTokenService refreshTokenService; + + @PostMapping("/refresh") + public ResponseEntity refresh(@RequestBody TokenRefreshRequest request) { + String requestToken = request.getRefreshToken(); + + return refreshTokenService.findByToken(requestToken) + .map(token -> { + if (refreshTokenService.isExpired(token)) { + return ResponseEntity.status(403).body("Refresh token expired"); + } + + User user = token.getUser(); + String newAccessToken = jwtService.generateToken(user); + return ResponseEntity.ok(new TokenRefreshResponse(newAccessToken, requestToken)); + }) + .orElseGet(() -> ResponseEntity.status(403).body("Invalid refresh token")); + } + } \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshRequest.java b/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshRequest.java new file mode 100644 index 0000000..3f76004 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshRequest.java @@ -0,0 +1,13 @@ +package com.skycrate.backend.skycrateBackend.dto; + +public class TokenRefreshRequest { + private String refreshToken; + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshResponse.java b/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshResponse.java new file mode 100644 index 0000000..fdcee72 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/dto/TokenRefreshResponse.java @@ -0,0 +1,17 @@ +package com.skycrate.backend.skycrateBackend.dto; + +public class TokenRefreshResponse { + private String accessToken; + private String refreshToken; + private String tokenType = "Bearer"; + + public TokenRefreshResponse(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + // Getters + public String getAccessToken() { return accessToken; } + public String getRefreshToken() { return refreshToken; } + public String getTokenType() { return tokenType; } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/entity/RefreshToken.java b/src/main/java/com/skycrate/backend/skycrateBackend/entity/RefreshToken.java new file mode 100644 index 0000000..fcc07aa --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/entity/RefreshToken.java @@ -0,0 +1,33 @@ +package com.skycrate.backend.skycrateBackend.entity; + +import jakarta.persistence.*; +import java.time.Instant; + +@Entity +public class RefreshToken { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String token; + + @OneToOne + @JoinColumn(name = "user_id", referencedColumnName = "id") + private User user; + + private Instant expiryDate; + + // Getters and setters + + public Long getId() { return id; } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public User getUser() { return user; } + public void setUser(User user) { this.user = user; } + + public Instant getExpiryDate() { return expiryDate; } + public void setExpiryDate(Instant expiryDate) { this.expiryDate = expiryDate; } +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java b/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..928d0e2 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/repository/RefreshTokenRepository.java @@ -0,0 +1,12 @@ +package com.skycrate.backend.skycrateBackend.repository; + +import com.skycrate.backend.skycrateBackend.entity.RefreshToken; +import com.skycrate.backend.skycrateBackend.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RefreshTokenRepository extends JpaRepository { + Optional findByToken(String token); + void deleteByUser(User user); +} \ No newline at end of file diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java b/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java new file mode 100644 index 0000000..0ba8314 --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/services/RefreshTokenService.java @@ -0,0 +1,42 @@ +package com.skycrate.backend.skycrateBackend.services; + +import com.skycrate.backend.skycrateBackend.entity.RefreshToken; +import com.skycrate.backend.skycrateBackend.entity.User; +import com.skycrate.backend.skycrateBackend.repository.RefreshTokenRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Service +public class RefreshTokenService { + + private final RefreshTokenRepository refreshTokenRepo; + + @Value("${security.jwt.refresh-expiry-ms:604800000}") // 7 days default + private Long refreshTokenDurationMs; + + public RefreshTokenService(RefreshTokenRepository refreshTokenRepo) { + this.refreshTokenRepo = refreshTokenRepo; + } + + public RefreshToken createRefreshToken(User user) { + refreshTokenRepo.deleteByUser(user); // Allow only 1 active token per user + + RefreshToken token = new RefreshToken(); + token.setUser(user); + token.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs)); + token.setToken(UUID.randomUUID().toString()); + return refreshTokenRepo.save(token); + } + + public Optional findByToken(String token) { + return refreshTokenRepo.findByToken(token); + } + + public boolean isExpired(RefreshToken token) { + return token.getExpiryDate().isBefore(Instant.now()); + } +} \ No newline at end of file