Users must be able to change their password, for instance when they think someone saw it. The route is PUT /me/password: PUT because it replaces a value the user already has, and /me because it is about the logged-in user. It goes through loginRequired, so its view receives user.
Changing a value of "me": the same pattern works for any value of the logged-in user. Here is a route that changes the user's email:
@PutMapping("/me/email")
Object changeEmail(HttpServletRequest request, @RequestBody Map<String, Object> data) {
return loginRequired(request, user -> saveEmail(user, data));
}
Object saveEmail(Map<String, Object> user, Map<String, Object> data) {
db.update("UPDATE users SET email = ? WHERE id = ?", data.get("email"), user.get("id"));
return Map.of("email", data.get("email"));
}
A password needs three more safety rules. Ask for the old password and check it against the stored hash: a stolen token, or a laptop left logged in, must not be enough to take the account, so a wrong old password gets 403. Store only the hash of the new password. Then delete all the user's tokens and end the session: every device, this one too, must log in again with the new password.
Ana is logged in twice: on her phone with the token <phone> and on her laptop with <token>. Your code must give these answers, in this order:
PUT /me/password {"old_password": "guess123", "new_password": "newsecret456"} (Bearer <token>)
→ 403 {"error": "Wrong password"}
PUT /me/password {"old_password": "secret123", "new_password": "newsecret456"} (Bearer <token>)
→ 200 {"message": "Password changed, please log in again"}
GET /me (Bearer <phone>) → 401 {"error": "Login required"}
GET /me (Bearer <token>) → 401 {"error": "Login required"}
POST /login {"email": "ana@example.com", "password": "secret123"} → 401 {"error": "Wrong email or password"}
POST /login {"email": "ana@example.com", "password": "newsecret456"} → 200 {"token": "<new>"}
GET /me (the new login's session) → 200 {"email": "ana@example.com", "id": 2, "role": "customer"}
Your task: below endSession, add PUT /me/password behind loginRequired, with a view updatePassword. Answer {"error": "Wrong password"} with 403 when the old password doesn't match. Otherwise store the new password's hash, delete all the user's tokens, end the session and answer {"message": "Password changed, please log in again"}.
Assignment
Follow the Coding Tutorial to build the changePassword route, one safety rule at a time.
Hintuser has no password hash in it: read it with db.queryForObject("SELECT password_hash FROM users WHERE id = ?", String.class, user.get("id")). Delete the tokens with WHERE user_id = ?, not WHERE token = ?, so every device is logged out.
Free plan: 30 tutor messages. PRO is unlimited.
