Right now /login answers every try. A script could send thousands of passwords for one email until one works. A rate limit stops it: the shop counts the failed logins of each email, and after 3 it answers 429 Too Many Requests without even checking the password.
Counting with a map: a HashMap can map each key to a count, and getOrDefault(key, 0) gives 0 for a key that isn't there yet. Here it counts the likes of each product:
static final Map<Integer, Integer> LIKES = new HashMap<>();
@PostMapping("/products/{productId}/like")
Object like(@PathVariable int productId) {
LIKES.put(productId, LIKES.getOrDefault(productId, 0) + 1);
return Map.of("likes", LIKES.get(productId));
}
Three details make the limit work. The check comes first, before the password: once the count is 3, even the right password gets 429, so a guesser can't tell a right guess from a wrong one. Emails with no account are counted too, so the answers look the same whether the account exists or not. And a right password sets the count back to 0, so three typos spread over a year don't lock anyone out.
Ana's password is secret123. Your code must give these answers, in this order:
POST /login {"email": "ana@example.com", "password": "guess1"} → 401 {"error": "Wrong email or password"}
POST /login {"email": "ana@example.com", "password": "guess2"} → 401 {"error": "Wrong email or password"}
POST /login {"email": "ana@example.com", "password": "secret123"} → 200 {"token": "<token>"}
POST /login "guess3", then "guess4", then "guess5" → 401 each time
POST /login {"email": "ana@example.com", "password": "secret123"} → 429 {"error": "Too many failed logins, try again later"}
POST /login {"email": "admin@example.com", "password": "admin-pass-123"} → 200 {"token": "<admin>"}
GET /me (Bearer <token>) → 200 {"email": "ana@example.com", "id": 2, "role": "customer"}
Your task: add the field FAILED_LOGINS, a Map<String, Integer> set to a new HashMap. At the start of login, answer {"error": "Too many failed logins, try again later"} with 429 when the email already has 3 or more failed tries. Add 1 to the email's count on every failed login, and set it back to 0 on a good one.
Assignment
Follow the Coding Tutorial to add a rate limit to login: the counter, the 429 check, counting failures and the reset.
Hint
Read a count with FAILED_LOGINS.getOrDefault(data.get("email"), 0): an email that never failed isn't a key yet, and get would give null. put needs the email as a String, so write (String) data.get("email") there. The 429 check is the first thing in login, before the user is even looked up.
Free plan: 30 tutor messages. PRO is unlimited.
