Fix the Password Reset Workflow
AmazonSoftware Engineer · Online AssessmentLast updated
Implement and debug a stateful password reset manager that handles token generation, time-to-live (TTL) expiry, brute-force attempt limits, and single-use token invalidation.
Problem Statement
You are tasked with building and fixing the core logic of an authentication service's password reset component. The service manages one-time verification tokens, handles expiration, and prevents brute-force attempts.
Implement the PasswordResetService class with the following methods:
class PasswordResetService:
def __init__(self, token_ttl: int, max_attempts: int):
"""
token_ttl: duration in seconds that a token remains valid.
max_attempts: maximum number of failed verification attempts allowed per token.
"""
pass
def register_user(self, user_id: str, password_hash: str) -> None:
"""
Registers a user with an initial password hash.
"""
pass
def request_reset(self, user_id: str, token: str, timestamp: int) -> bool:
"""
Generates a reset request for user_id with the provided token at the given timestamp.
If user_id does not exist, returns False.
If a previous token was active for this user, it is immediately invalidated and replaced.
Returns True on success.
"""
pass
def verify_and_reset(self, user_id: str, token: str, new_password_hash: str, timestamp: int) -> bool:
"""
Attempts to reset the password for user_id.
Returns True if successful, or False if the reset fails.
"""
pass
Rules & Constraints
- Expiration: A token requested at
timestamp = tis valid fort t + token_ttlmust fail and invalidate the token. - Attempt Limits: Each call to
verify_and_resetwith an incorrect token increments the failure count for the active token. If the failure count reachesmax_attempts, the token is immediately revoked (subsequent attempts fail even if given the right token). - Single-Use: A successfully verified token is immediately consumed and cannot be reused.
- Password Change:
new_password_hashcannot be identical to the user's currentpassword_hash. If identical, the attempt fails (counts as a failed attempt). - Unknown Users: Calls referencing a non-existent
user_idshould returnFalsewithout throwing errors.
Example
service = PasswordResetService(token_ttl=300, max_attempts=3)
service.register_user("user_1", "hash_old")
# Request reset at t=100 with token "ABC"
assert service.request_reset("user_1", "ABC", 100) == True
# Failed attempt 1 at t=120
assert service.verify_and_reset("user_1", "WRONG", "hash_new", 120) == False
# Attempt with old password at t=130 (counts as failed attempt 2)
assert service.verify_and_reset("user_1", "ABC", "hash_old", 130) == False
# Successful reset at t=140
assert service.verify_and_reset("user_1", "ABC", "hash_new", 140) == True
# Reusing the token at t=150 must fail
assert service.verify_and_reset("user_1", "ABC", "hash_another", 150) == False
Hints
Hint 1
Track active tokens per user using a record or dataclass storing token value, creation timestamp, and remaining or failed attempt count.
Hint 2
Be careful with the order of validation in verify_and_reset: check existence, check expiration, compare the token and password, and update attempt counts accordingly.
Hint 3
Make sure that invalidating a token on expiration or exceeding max_attempts completely removes or clears the active token state for that user.
Follow-up questions
- How would you prevent timing attacks when comparing the supplied token against the stored token?
- How would you scale this design to a distributed environment where multiple application servers handle reset requests concurrently?
- If we wanted to enforce rate-limiting on calls to request_reset itself (e.g., max 3 requests per hour), what data structure or algorithm would you introduce?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.