Skip to content
Open
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 change: 1 addition & 0 deletions RestroHub/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logs/
2 changes: 2 additions & 0 deletions RestroHub/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ dependencies {
// Logging (JSON format for production)
implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5'
implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5'

implementation 'org.springframework.boot:spring-boot-starter-mail'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ public ResponseEntity<ApiResponse<AuthResponse>> login(
return ResponseEntity.ok(ApiResponse.success(authResponse, "Login successful"));
}



@PostMapping("/refresh")
@Operation(
summary = "Refresh access token",
Expand Down Expand Up @@ -147,7 +149,36 @@ public ResponseEntity<ApiResponse<AuthResponse>> refreshToken(

return ResponseEntity.ok(ApiResponse.success(authResponse, "Token refreshed successfully"));
}

@PostMapping("/forgot-password")
public ResponseEntity<String> forgotPassword(@RequestParam String email) {
if(email == null || email.trim().isEmpty()) {
return ResponseEntity.badRequest().body("Email cannot be empty");
}

if(!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
return ResponseEntity.badRequest().body("Invalid email format");
}
authService.forgotPassword(email);

return ResponseEntity.ok("Reset token generated successfully");
}

@PostMapping("/reset-password")
public ResponseEntity<String> resetPassword(
@RequestParam String token,
@RequestParam String newPassword) {
if(newPassword == null || newPassword.trim().isEmpty()) {
return ResponseEntity.badRequest().body("Password cannot be empty");
}

if(newPassword.length() < 6) {
return ResponseEntity.badRequest().body("Password must be at least 6 characters");
}
Comment on lines +166 to +176
authService.resetPassword(token, newPassword);

return ResponseEntity.ok("Password reset successful");
}
Comment on lines +153 to +180

@PostMapping("/logout")
@Operation(
summary = "Logout user",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public interface AuthService {
AuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest);

void logout(String token);

void forgotPassword(String email);

void resetPassword(String token, String newPassword);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,25 @@
import java.util.List;
import java.util.stream.Collectors;

import com.restroly.qrmenu.user.entity.User;
import com.restroly.qrmenu.user.repository.UserRepository;

import java.time.LocalDateTime;
import java.util.UUID;
import org.springframework.security.crypto.password.PasswordEncoder;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {


private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;

@Override
public AuthResponse login(LoginRequest loginRequest) {
Expand Down Expand Up @@ -116,4 +127,39 @@ public void logout(String token) {
SecurityContextHolder.clearContext();
log.info("User logged out successfully");
}

@Override
public void forgotPassword(String email) {

User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found"));

String token = UUID.randomUUID().toString();

user.setResetToken(token);
user.setResetTokenExpiry(LocalDateTime.now().plusMinutes(30));

userRepository.save(user);

emailService.sendResetEmail(user.getEmail(), token);
Comment on lines +133 to +144
}
@Override
public void resetPassword(String token, String newPassword) {

User user = userRepository.findByResetToken(token)
.orElseThrow(() -> new RuntimeException("Invalid reset token"));

if (user.getResetTokenExpiry().isBefore(LocalDateTime.now())) {
throw new RuntimeException("Reset token expired");
}

user.setPassword(passwordEncoder.encode(newPassword));

user.setResetToken(null);
user.setResetTokenExpiry(null);

userRepository.save(user);

System.out.println("Password reset successful");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.restroly.qrmenu.auth.service;

import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class EmailService {

private final JavaMailSender mailSender;

public void sendResetEmail(String toEmail, String token) {

SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(System.getenv("MAIL_USERNAME"));
message.setTo(toEmail);
message.setSubject("Password Reset Request");
Comment on lines +16 to +19

message.setText(
"Click the link below to reset your password:\n\n"
+ "https://example.com/reset-password?token="
+ token
);

mailSender.send(message);

System.out.println("Reset email sent successfully");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class SecurityConfig {
"/api/v1/users/register",
// Public api endpoints
"/public/api/v1/**"


};

private static final String[] PUBLIC_GET_URLS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public class User {
@Column(name = "user_password", nullable = false)
private String password;

@Column(name = "reset_token")
private String resetToken;

@Column(name = "reset_token_expiry")
private LocalDateTime resetTokenExpiry;

@Column(name = "phone_number")
private String phoneNumber;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByEmail(String email);

Optional<User> findByResetToken(String resetToken);

boolean existsByEmail(String email);

boolean existsByEmailAndUserIdNot(String email, Long userId);
Expand Down
4 changes: 2 additions & 2 deletions RestroHub/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# ===============================
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/RestroHub_DB
spring.datasource.username=${DB_USERNAME:postgres}
spring.datasource.password=${DB_PASSWORD:postgres}
spring.datasource.password=${DB_PASSWORD:${DB_PASSWORD}}
spring.datasource.driver-class-name=org.postgresql.Driver
# ===============================
# HikariCP
Expand Down Expand Up @@ -52,4 +52,4 @@ spring.servlet.multipart.max-request-size=10MB
security.jwt.secret=${JWT_SECRET:your-256-bit-secret-key-here-change-in-production}
security.jwt.expiration=${JWT_EXPIRATION:86400000}
security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:604800000}
security.cors.allowed-origins=http://localhost:5173,http://localhost:3000,http://localhost:3002
security.cors.allowed-origins=http://localhost:5173,http://localhost:3000,http://localhost:3002