Katkat

Fix the Password Reset Workflow

AmazonSoftware Engineer · Online Assessment

Last 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

  1. Expiration: A token requested at timestamp = t is valid for t t + token_ttl must fail and invalidate the token.
  2. Attempt Limits: Each call to verify_and_reset with an incorrect token increments the failure count for the active token. If the failure count reaches max_attempts, the token is immediately revoked (subsequent attempts fail even if given the right token).
  3. Single-Use: A successfully verified token is immediately consumed and cannot be reused.
  4. Password Change: new_password_hash cannot be identical to the user's current password_hash. If identical, the attempt fails (counts as a failed attempt).
  5. Unknown Users: Calls referencing a non-existent user_id should return False without 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?