From 067532dc6dc8c29b4dfbafbe91389a4ca67f53c4 Mon Sep 17 00:00:00 2001 From: Dilina Raveen Date: Thu, 2 Oct 2025 14:27:02 +0530 Subject: [PATCH 1/2] feature: password reset via OTP + example local config --- pom.xml | 5 + .../hacktober/blog/config/FirebaseConfig.java | 56 +++++++++ .../blog/config/FirestoreService.java | 35 ------ .../hacktober/blog/config/RedisConfig.java | 41 ++++--- .../hacktober/blog/email/EmailController.java | 3 +- .../hacktober/blog/email/EmailRequest.java | 21 ++-- .../hacktober/blog/email/EmailService.java | 6 + .../hacktober/blog/login/LoginService.java | 49 ++++++-- .../com/hacktober/blog/otp/OtpService.java | 53 ++++++++- .../hacktober/blog/user/UserController.java | 2 + .../com/hacktober/blog/user/UserService.java | 110 +++++++++++------- src/main/resources/application.properties | 22 +++- 12 files changed, 282 insertions(+), 121 deletions(-) create mode 100644 src/main/java/com/hacktober/blog/config/FirebaseConfig.java delete mode 100644 src/main/java/com/hacktober/blog/config/FirestoreService.java diff --git a/pom.xml b/pom.xml index 0a14c9b..30a51d3 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,11 @@ 2.6.0 + + org.springframework.boot + spring-boot-starter-validation + + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/hacktober/blog/config/FirebaseConfig.java b/src/main/java/com/hacktober/blog/config/FirebaseConfig.java new file mode 100644 index 0000000..fb0aad6 --- /dev/null +++ b/src/main/java/com/hacktober/blog/config/FirebaseConfig.java @@ -0,0 +1,56 @@ +package com.hacktober.blog.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.FileInputStream; +import java.io.IOException; + +@Configuration +public class FirebaseConfig { + private static final Logger log = LoggerFactory.getLogger(FirebaseConfig.class); + + @Value("${firebase.credentials.path:${FIREBASE_ADMIN_SA_PATH:}}") + private String credentialsPath; + + @Value("${firebase.project-id:${FIREBASE_PROJECT_ID:}}") + private String projectId; + + @Bean + public FirebaseApp firebaseApp() throws IOException { + if (!FirebaseApp.getApps().isEmpty()) return FirebaseApp.getInstance(); + + FirebaseOptions.Builder builder = FirebaseOptions.builder(); + + if (credentialsPath != null && !credentialsPath.isBlank()) { + try (FileInputStream in = new FileInputStream(credentialsPath)) { + builder.setCredentials(GoogleCredentials.fromStream(in)); + log.info("Firebase: using service account from {}", credentialsPath); + } + } else { + builder.setCredentials(GoogleCredentials.getApplicationDefault()); + log.info("Firebase: using application default credentials"); + } + + if (projectId != null && !projectId.isBlank()) builder.setProjectId(projectId); + + FirebaseApp app = FirebaseApp.initializeApp(builder.build()); + log.info("Firebase initialized (projectId={})", projectId); + return app; + } + + @Bean + public Firestore firestore(FirebaseApp app) { + FirestoreOptions.Builder fo = FirestoreOptions.newBuilder(); + if (projectId != null && !projectId.isBlank()) fo.setProjectId(projectId); + return fo.build().getService(); + } +} diff --git a/src/main/java/com/hacktober/blog/config/FirestoreService.java b/src/main/java/com/hacktober/blog/config/FirestoreService.java deleted file mode 100644 index 59a3774..0000000 --- a/src/main/java/com/hacktober/blog/config/FirestoreService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.hacktober.blog.config; - -import java.io.FileInputStream; -import java.io.IOException; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; - -@SuppressWarnings("deprecation") -@Configuration -public class FirestoreService { - - @Bean - public FirebaseApp initFirebaseApp() { - - try { - - FileInputStream serviceAccount = new FileInputStream("/etc/secrets/firebaseServiceAccountKey.json"); - FirebaseOptions options = new FirebaseOptions.Builder().setCredentials(GoogleCredentials.fromStream(serviceAccount)) - .build(); - return FirebaseApp.initializeApp(options); - - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; - - } - -} diff --git a/src/main/java/com/hacktober/blog/config/RedisConfig.java b/src/main/java/com/hacktober/blog/config/RedisConfig.java index 9dd5357..a9998cb 100644 --- a/src/main/java/com/hacktober/blog/config/RedisConfig.java +++ b/src/main/java/com/hacktober/blog/config/RedisConfig.java @@ -3,27 +3,40 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import redis.clients.jedis.DefaultJedisClientConfig; -import redis.clients.jedis.JedisClientConfig; -import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.UnifiedJedis; @Configuration public class RedisConfig { - - @Value("${redis.password}") - private String password; - @Bean + @Value("${redis.host:127.0.0.1}") + private String host; + + @Value("${redis.port:6379}") + private int port; + + @Value("${redis.password:}") + private String password; + + @Value("${redis.ssl:false}") + private boolean ssl; + + @Value("${redis.timeout-ms:2000}") + private int timeoutMs; + + @Value("${redis.client-name:hacktoberblog-backend}") + private String clientName; + + @Bean(destroyMethod = "close") public UnifiedJedis unifiedJedis() { - JedisClientConfig config = DefaultJedisClientConfig.builder() - .user("default") - .password(password) + DefaultJedisClientConfig cfg = DefaultJedisClientConfig.builder() + .password((password != null && !password.isBlank()) ? password : null) + .ssl(ssl) + .connectionTimeoutMillis(timeoutMs) + .socketTimeoutMillis(timeoutMs) + .clientName(clientName) .build(); - return new UnifiedJedis( - new HostAndPort("redis-14860.crce182.ap-south-1-1.ec2.redns.redis-cloud.com", 14860), - config - ); + return new UnifiedJedis(new HostAndPort(host, port), cfg); } } diff --git a/src/main/java/com/hacktober/blog/email/EmailController.java b/src/main/java/com/hacktober/blog/email/EmailController.java index f8261bb..afafaaa 100644 --- a/src/main/java/com/hacktober/blog/email/EmailController.java +++ b/src/main/java/com/hacktober/blog/email/EmailController.java @@ -1,6 +1,7 @@ package com.hacktober.blog.email; import com.hacktober.blog.utils.ApiResponse; +import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -21,7 +22,7 @@ public EmailController(EmailService emailService) { @PostMapping("/send") @Operation(summary = "Send email", description = "Dispatch an email message using the configured SMTP provider.") - public ResponseEntity> sendMail(@RequestBody EmailRequest request) { + public ResponseEntity> sendMail(@Valid @RequestBody EmailRequest request) { emailService.sendEmail(request.getTo(), request.getSubject(), request.getBody()); String message = "Email sent successfully to " + request.getTo(); return ResponseEntity.ok(ApiResponse.success(message, "Email sent successfully")); diff --git a/src/main/java/com/hacktober/blog/email/EmailRequest.java b/src/main/java/com/hacktober/blog/email/EmailRequest.java index 5dae6ad..886212e 100644 --- a/src/main/java/com/hacktober/blog/email/EmailRequest.java +++ b/src/main/java/com/hacktober/blog/email/EmailRequest.java @@ -1,17 +1,22 @@ package com.hacktober.blog.email; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor public class EmailRequest { + + @NotBlank + @Email String to; + @NotBlank String subject; + @NotBlank String body; - - public EmailRequest(String to, String subject, String body) { - super(); - this.to = to; - this.subject = subject; - this.body = body; - } - + public String getTo() { return to; } diff --git a/src/main/java/com/hacktober/blog/email/EmailService.java b/src/main/java/com/hacktober/blog/email/EmailService.java index ea2689b..9a94b6a 100644 --- a/src/main/java/com/hacktober/blog/email/EmailService.java +++ b/src/main/java/com/hacktober/blog/email/EmailService.java @@ -23,4 +23,10 @@ public void sendEmail(String to, String subject, String text) { message.setText(text); mailSender.send(message); } + public void sendPasswordResetOtp(String to, String otp) { + String subject = "Your HacktoberBlog password reset code"; + String body = "Use this code to reset your password: " + otp + "\nIt expires in 10 minutes."; + sendEmail(to, subject, body); + } + } diff --git a/src/main/java/com/hacktober/blog/login/LoginService.java b/src/main/java/com/hacktober/blog/login/LoginService.java index 301109c..0bcd3c1 100644 --- a/src/main/java/com/hacktober/blog/login/LoginService.java +++ b/src/main/java/com/hacktober/blog/login/LoginService.java @@ -2,10 +2,11 @@ import java.util.concurrent.ExecutionException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.DocumentSnapshot; -import com.google.cloud.firestore.Firestore; import com.google.firebase.cloud.FirestoreClient; import com.hacktober.blog.user.User; import com.hacktober.blog.utils.Utils; @@ -13,20 +14,46 @@ @Service public class LoginService { - public boolean login(String username, String password) throws InterruptedException, ExecutionException { - Firestore db = FirestoreClient.getFirestore(); - DocumentSnapshot doc = db.collection("users").document(username).get().get(); + private final PasswordEncoder passwordEncoder; - if (!doc.exists()) { - return false; // user not found - } + public LoginService(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + public boolean login(String username, String rawPassword) throws InterruptedException, ExecutionException { + DocumentReference docRef = FirestoreClient.getFirestore() + .collection("users") + .document(username); + + DocumentSnapshot doc = docRef.get().get(); + if (!doc.exists()) return false; User user = doc.toObject(User.class); - if (user == null) { - return false; + if (user == null || user.getPassword() == null) return false; + + String stored = user.getPassword(); + + // 1) If it's BCrypt, verify with PasswordEncoder + if (isBcrypt(stored)) { + return passwordEncoder.matches(rawPassword, stored); } - // Encrypt the incoming password and compare - return Utils.match(password, user.getPassword()); + // 2) Legacy path (Base64 via Utils) — if it matches, rehash to BCrypt now + boolean legacyOk = Utils.match(rawPassword, stored); + if (legacyOk) { + try { + String bcrypt = passwordEncoder.encode(rawPassword); + docRef.update("password", bcrypt).get(); // silent upgrade + } catch (Exception ignored) { + + } + } + return legacyOk; + } + + private boolean isBcrypt(String value) { + if (value == null) return false; + // BCrypt hashes commonly start with $2a$, $2b$, or $2y$ + return value.startsWith("$2a$") || value.startsWith("$2b$") || value.startsWith("$2y$"); } } diff --git a/src/main/java/com/hacktober/blog/otp/OtpService.java b/src/main/java/com/hacktober/blog/otp/OtpService.java index 2074851..fcc5532 100644 --- a/src/main/java/com/hacktober/blog/otp/OtpService.java +++ b/src/main/java/com/hacktober/blog/otp/OtpService.java @@ -5,27 +5,68 @@ import redis.clients.jedis.UnifiedJedis; import java.security.SecureRandom; +import java.time.Duration; @Service public class OtpService { private final UnifiedJedis jedis; + private static final SecureRandom RNG = new SecureRandom(); + + private static final int OTP_TTL_SEC = (int) Duration.ofMinutes(10).toSeconds(); + private static final int MAX_REQUESTS_PER_HOUR = 5; + + public OtpService(UnifiedJedis jedis) { this.jedis = jedis; } + private static String otpKey(String email) { + return "otp:reset:" + email.toLowerCase(); + } + + private static String countKey(String email) { + return "otp:reset:count:" + email.toLowerCase(); + } + + /** + * Generates a 6-digit OTP, stores it with TTL, and returns it. + * Throws IllegalStateException if caller exceeded hourly request limit. + */ public String generateOtp(String email) { - // Generate a 6 digit OTP using Java library e.g. SecureRandom - // Set the key as email and value as OTP, with expiration time of 5-6 minutes using jedis object + // Simple per-email rate limit + String cKey = countKey(email); + Long count = jedis.incr(cKey); + if (count != null && count == 1) { + // first request in this window -> start 1h expiry + jedis.expire(cKey, 3600); + } + if (count != null && count > MAX_REQUESTS_PER_HOUR) { + throw new IllegalStateException("Too many OTP requests. Try again later."); + } - return null; + String code = String.format("%06d", RNG.nextInt(1_000_000)); // 000000–999999 + // SET with expiry (overwrite any previous pending OTP) + jedis.setex(otpKey(email), OTP_TTL_SEC, code); + return code; } + /** + * Validates the OTP and consumes it (one-time). Returns true if valid. + */ public boolean verifyOtp(String email, String inputOtp) { - // Verify OTP function - // Once verified, delete the key value pair - return false; + String key = otpKey(email); + String expected = jedis.get(key); + if (expected == null) { + return false; // expired or never issued + } + boolean ok = expected.equals(inputOtp); + if (ok) { + // one-time use: delete after successful validation + jedis.del(key); + } + return ok; } diff --git a/src/main/java/com/hacktober/blog/user/UserController.java b/src/main/java/com/hacktober/blog/user/UserController.java index 21617e0..1598d13 100644 --- a/src/main/java/com/hacktober/blog/user/UserController.java +++ b/src/main/java/com/hacktober/blog/user/UserController.java @@ -19,6 +19,7 @@ public class UserController { private final UserService userService; + public UserController(UserService userService) { this.userService = userService; } @@ -62,4 +63,5 @@ public ResponseEntity> deleteUser(@PathVariable String usern String result = userService.delete(username); return ResponseEntity.ok(ApiResponse.success(result, "User deleted successfully")); } + } diff --git a/src/main/java/com/hacktober/blog/user/UserService.java b/src/main/java/com/hacktober/blog/user/UserService.java index 3b3443c..14170a5 100644 --- a/src/main/java/com/hacktober/blog/user/UserService.java +++ b/src/main/java/com/hacktober/blog/user/UserService.java @@ -1,9 +1,11 @@ package com.hacktober.blog.user; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import com.google.api.core.ApiFuture; @@ -19,15 +21,18 @@ public class UserService { private static final String USERNAMES_DOC = "usernames"; private final EmailService emailService; + private final PasswordEncoder passwordEncoder; + // Inject EmailService through constructor - public UserService(EmailService emailService) { + public UserService(EmailService emailService, PasswordEncoder passwordEncoder) { this.emailService = emailService; + this.passwordEncoder = passwordEncoder; } /** Create User + Send Email */ public String create(User user) throws InterruptedException, ExecutionException { Firestore db = FirestoreClient.getFirestore(); - user.setPassword(Utils.encode(user.getPassword())); // Encrypt password + user.setPassword(passwordEncoder.encode(user.getPassword())); // Save user to Firestore ApiFuture result = db.collection(COLLECTION_NAME).document(user.getUsername()).set(user); @@ -68,7 +73,7 @@ public List getAll() throws InterruptedException, ExecutionException { public String update(User user) throws InterruptedException, ExecutionException { Firestore db = FirestoreClient.getFirestore(); if (user.getPassword() != null) { - user.setPassword(Utils.encode(user.getPassword())); + user.setPassword(passwordEncoder.encode(user.getPassword())); } ApiFuture result = db.collection(COLLECTION_NAME).document(user.getUsername()).set(user); return result.get().getUpdateTime().toString(); @@ -82,45 +87,66 @@ public String delete(String username) throws InterruptedException, ExecutionExce removeUsername(username); return result.get().getUpdateTime().toString(); } - + public List getAllUsernames() throws InterruptedException, ExecutionException { - Firestore db = FirestoreClient.getFirestore(); - DocumentReference docRef = db.collection(COLLECTION_NAME).document(USERNAMES_DOC); - DocumentSnapshot snapshot = docRef.get().get(); - - if (snapshot.exists() && snapshot.contains("usernames")) { - System.out.println(snapshot.get("usernames")); - return (List) snapshot.get("usernames"); - } - return new ArrayList<>(); - } - - /** Add or remove username from usernames array */ - public String updateUsernames(String username, boolean add) throws InterruptedException, ExecutionException { - Firestore db = FirestoreClient.getFirestore(); - DocumentReference docRef = db.collection("usernames").document("usernames"); - - List currentUsernames = getAllUsernames(); - if (add) { - if (!currentUsernames.contains(username)) { - currentUsernames.add(username); - } - } else { - currentUsernames.remove(username); - } - - ApiFuture result = docRef.update("usernames", currentUsernames); - return result.get().getUpdateTime().toString(); - } - - /** Helper: add a username */ - private void addUsername(String username) throws InterruptedException, ExecutionException { - updateUsernames(username, true); - } - - /** Helper: remove a username */ - public void removeUsername(String username) throws InterruptedException, ExecutionException { - updateUsernames(username, false); - } + Firestore db = FirestoreClient.getFirestore(); + DocumentReference docRef = db.collection(COLLECTION_NAME).document(USERNAMES_DOC); // users/usernames + DocumentSnapshot snapshot = docRef.get().get(); + + if (snapshot.exists() && snapshot.contains("usernames")) { + return (List) snapshot.get("usernames"); + } + return new ArrayList<>(); + } + + + /** Add or remove username using atomic array ops; creates doc if missing */ + public String updateUsernames(String username, boolean add) throws InterruptedException, ExecutionException { + Firestore db = FirestoreClient.getFirestore(); + DocumentReference docRef = db.collection(COLLECTION_NAME).document(USERNAMES_DOC); // users/usernames + + // Ensure the doc exists without clobbering existing fields + docRef.set(Collections.singletonMap("usernames", Collections.emptyList()), SetOptions.merge()).get(); + + ApiFuture write = add + ? docRef.update("usernames", FieldValue.arrayUnion(username)) + : docRef.update("usernames", FieldValue.arrayRemove(username)); + + return write.get().getUpdateTime().toString(); + } + + /** Helper: add a username */ + private void addUsername(String username) throws InterruptedException, ExecutionException { + updateUsernames(username, true); + } + + /** Helper: remove a username */ + public void removeUsername(String username) throws InterruptedException, ExecutionException { + updateUsernames(username, false); + } + + /** Reset password by email (returns true if a user was updated) */ + public boolean resetPasswordByEmail(String email, String rawNewPassword) { + try { + Firestore db = FirestoreClient.getFirestore(); + // Find the user doc by email (username is the doc id, but we don't know it here) + ApiFuture future = db.collection(COLLECTION_NAME) + .whereEqualTo("email", email) + .limit(1) + .get(); + + List docs = future.get().getDocuments(); + if (docs.isEmpty()) { + return false; + } + + DocumentReference docRef = docs.get(0).getReference(); + String hash = passwordEncoder.encode(rawNewPassword); + docRef.update("password", hash).get(); + return true; + } catch (Exception e) { + throw new RuntimeException("Failed to reset password", e); + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a8714d5..f5cad74 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,10 +1,24 @@ +# --- Firebase (local dev) --- +firebase.credentials.path=/ABSOLUTE/PATH/TO/firebaseServiceAccountKey.json +firebase.project-id=YOUR_FIREBASE_PROJECT_ID + +# --- App --- spring.application.name=blog + +# --- Mail (use env var for password) --- spring.mail.host=smtp.gmail.com spring.mail.port=587 -spring.mail.username=${GMAIL_ACCOUNT} -spring.mail.password=${GMAIL_APP_KEY} +spring.mail.username=your_email@gmail.com +spring.mail.password=${MAIL_PASSWORD:} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true -redis.password = ${REDIS_PASSWORD} -notifications.enabled=${NOTIFICATIONS_ENABLED:true} \ No newline at end of file + +# --- Redis (local dev) --- +redis.host=127.0.0.1 +redis.port=6379 +redis.password=${REDIS_PASSWORD:} +redis.ssl=false + +# --- Feature flags --- +notifications.enabled=${NOTIFICATIONS_ENABLED:true} From f9dc26924d3817e2a55fb7f60843803940b47e71 Mon Sep 17 00:00:00 2001 From: Dilina Raveen Date: Thu, 2 Oct 2025 21:24:41 +0530 Subject: [PATCH 2/2] feature: password reset via OTP + example local config --- .../hacktober/blog/login/LoginService.java | 49 ++++------------- .../com/hacktober/blog/otp/OtpService.java | 53 +++---------------- .../hacktober/blog/user/UserController.java | 2 - 3 files changed, 17 insertions(+), 87 deletions(-) diff --git a/src/main/java/com/hacktober/blog/login/LoginService.java b/src/main/java/com/hacktober/blog/login/LoginService.java index 0bcd3c1..301109c 100644 --- a/src/main/java/com/hacktober/blog/login/LoginService.java +++ b/src/main/java/com/hacktober/blog/login/LoginService.java @@ -2,11 +2,10 @@ import java.util.concurrent.ExecutionException; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.DocumentSnapshot; +import com.google.cloud.firestore.Firestore; import com.google.firebase.cloud.FirestoreClient; import com.hacktober.blog.user.User; import com.hacktober.blog.utils.Utils; @@ -14,46 +13,20 @@ @Service public class LoginService { - private final PasswordEncoder passwordEncoder; + public boolean login(String username, String password) throws InterruptedException, ExecutionException { + Firestore db = FirestoreClient.getFirestore(); + DocumentSnapshot doc = db.collection("users").document(username).get().get(); - public LoginService(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - } - - public boolean login(String username, String rawPassword) throws InterruptedException, ExecutionException { - DocumentReference docRef = FirestoreClient.getFirestore() - .collection("users") - .document(username); - - DocumentSnapshot doc = docRef.get().get(); - if (!doc.exists()) return false; - - User user = doc.toObject(User.class); - if (user == null || user.getPassword() == null) return false; - - String stored = user.getPassword(); - - // 1) If it's BCrypt, verify with PasswordEncoder - if (isBcrypt(stored)) { - return passwordEncoder.matches(rawPassword, stored); + if (!doc.exists()) { + return false; // user not found } - // 2) Legacy path (Base64 via Utils) — if it matches, rehash to BCrypt now - boolean legacyOk = Utils.match(rawPassword, stored); - if (legacyOk) { - try { - String bcrypt = passwordEncoder.encode(rawPassword); - docRef.update("password", bcrypt).get(); // silent upgrade - } catch (Exception ignored) { - - } + User user = doc.toObject(User.class); + if (user == null) { + return false; } - return legacyOk; - } - private boolean isBcrypt(String value) { - if (value == null) return false; - // BCrypt hashes commonly start with $2a$, $2b$, or $2y$ - return value.startsWith("$2a$") || value.startsWith("$2b$") || value.startsWith("$2y$"); + // Encrypt the incoming password and compare + return Utils.match(password, user.getPassword()); } } diff --git a/src/main/java/com/hacktober/blog/otp/OtpService.java b/src/main/java/com/hacktober/blog/otp/OtpService.java index fcc5532..2074851 100644 --- a/src/main/java/com/hacktober/blog/otp/OtpService.java +++ b/src/main/java/com/hacktober/blog/otp/OtpService.java @@ -5,68 +5,27 @@ import redis.clients.jedis.UnifiedJedis; import java.security.SecureRandom; -import java.time.Duration; @Service public class OtpService { private final UnifiedJedis jedis; - private static final SecureRandom RNG = new SecureRandom(); - - private static final int OTP_TTL_SEC = (int) Duration.ofMinutes(10).toSeconds(); - private static final int MAX_REQUESTS_PER_HOUR = 5; - - public OtpService(UnifiedJedis jedis) { this.jedis = jedis; } - private static String otpKey(String email) { - return "otp:reset:" + email.toLowerCase(); - } - - private static String countKey(String email) { - return "otp:reset:count:" + email.toLowerCase(); - } - - /** - * Generates a 6-digit OTP, stores it with TTL, and returns it. - * Throws IllegalStateException if caller exceeded hourly request limit. - */ public String generateOtp(String email) { - // Simple per-email rate limit - String cKey = countKey(email); - Long count = jedis.incr(cKey); - if (count != null && count == 1) { - // first request in this window -> start 1h expiry - jedis.expire(cKey, 3600); - } - if (count != null && count > MAX_REQUESTS_PER_HOUR) { - throw new IllegalStateException("Too many OTP requests. Try again later."); - } + // Generate a 6 digit OTP using Java library e.g. SecureRandom + // Set the key as email and value as OTP, with expiration time of 5-6 minutes using jedis object - String code = String.format("%06d", RNG.nextInt(1_000_000)); // 000000–999999 - // SET with expiry (overwrite any previous pending OTP) - jedis.setex(otpKey(email), OTP_TTL_SEC, code); - return code; + return null; } - /** - * Validates the OTP and consumes it (one-time). Returns true if valid. - */ public boolean verifyOtp(String email, String inputOtp) { - String key = otpKey(email); - String expected = jedis.get(key); - if (expected == null) { - return false; // expired or never issued - } - boolean ok = expected.equals(inputOtp); - if (ok) { - // one-time use: delete after successful validation - jedis.del(key); - } - return ok; + // Verify OTP function + // Once verified, delete the key value pair + return false; } diff --git a/src/main/java/com/hacktober/blog/user/UserController.java b/src/main/java/com/hacktober/blog/user/UserController.java index 1598d13..21617e0 100644 --- a/src/main/java/com/hacktober/blog/user/UserController.java +++ b/src/main/java/com/hacktober/blog/user/UserController.java @@ -19,7 +19,6 @@ public class UserController { private final UserService userService; - public UserController(UserService userService) { this.userService = userService; } @@ -63,5 +62,4 @@ public ResponseEntity> deleteUser(@PathVariable String usern String result = userService.delete(username); return ResponseEntity.ok(ApiResponse.success(result, "User deleted successfully")); } - }