Fix the Password Reset Workflow
Company: Amazon
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Take-home Project
You are given a small code repository that implements a **password reset flow**. The repository ships broken: several bugs prevent the application from working correctly, and the test suite does not pass. Your job is to debug it locally, find the broken files, fix both the implementation and configuration issues, and make the full test suite green.
This is a "fix the bugs" exercise, not a from-scratch build — the expected fixes are small and surgical, usually a few lines each. You are graded on how you **diagnose and isolate** failures as much as on the final patch.
The flow is the standard three-step password reset, and the intended (correct) behavior is:
1. **Request** — when a user asks to reset their password, the system generates a one-time **verification code** and stores it for that user.
2. **Verify** — a submitted code is accepted **only if** it matches the stored code **and** the code was generated **no more than 30 seconds ago**; otherwise it is rejected.
3. **Reset** — once verification passes, the new password is set and **persisted to the user's profile** (not merely returned in a success response).
The repository currently has these defects (some stated in the prompt, others you must discover by running it):
- The verification code is **never actually generated** during the request step.
- The verify step does **not enforce the 30-second expiry window** (and/or never records when the code was generated).
- After a successful reset, the **new password is not persisted** to the user's profile.
- The **test suite fails to load** because a URL/route or config file references **undefined variables**, so even correct business logic can't be exercised until this is fixed.
Walk through how you would approach this: reproduce the failures, isolate each root cause, apply minimal targeted fixes, and verify both interactively and via the test suite.
```hint Where to start
Don't guess-and-edit. Follow the README to run the app, then run the tests, and treat the **first failing stack trace** as your map — the file and line it names is almost always where the fix goes. Fix one failure, re-run, repeat.
```
```hint Separate the two failure classes
A test that fails because the **harness itself can't import/collect** (e.g. an undefined variable in a routes/URL config file) is a *different* class of bug from a business-logic failure, and one broken config can mask all the real bugs. If *every* test fails identically at collection/import time, suspect the harness first — fix that before trusting any assertion results.
```
```hint The three logic bugs
For generation: produce and **store** the code *plus a `generated_at` timestamp* on the request (Bug 2 needs that timestamp). For verify: handle three distinct cases — no code exists, code mismatches, and `now - generated_at > 30s` — watching the comparison direction (`>` vs `<=`), consistent **time units**, and **timezone-aware vs naive** datetimes. For reset: make sure you actually **commit the new password back through the ORM/repository/DB layer**, not just return success.
```
### Constraints & Assumptions
- This is a **take-home / online-assessment style debugging task**. Fixes are expected to be small and targeted, not a rewrite, and the public behavior should not change beyond fixing the four defects.
- The repository builds and dependencies install; the failures are logic/config defects, not environment setup. No external network access is needed to run the suite.
- The expiry window is exactly **30 seconds**, measured from code generation to verification.
- Assume a single-process, simple persistence layer (dict / ORM / repository); no distributed design is required.
- A README documents how to run the app and the tests; treat it as authoritative for commands and entry points.
- For the OA itself a simple numeric/random code is acceptable if that is what the tests expect, but you should be able to articulate the production-grade alternative.
### Clarifying Questions to Ask
- Is the 30-second window measured from code **generation** or from the **request being submitted**, and is it inclusive at exactly 30s (`<= 30s` vs `< 30s`)?
- Should the verification code be **single-use** (invalidated after a successful reset or a failed verify), or retryable until it expires?
- Are passwords stored **hashed** or in plaintext for this exercise, and should the verification **code** be hashed at rest too?
- Are timestamps stored as UTC, local time, or epoch seconds, and is the codebase timezone-aware?
- Should I preserve the existing API contract and route names exactly, or refactor freely as long as tests pass?
- Does "make the full test suite pass" include only the existing tests, or should I also add tests for the bugs I fix?
### What a Strong Answer Covers
- **Disciplined reproduction**: runs the app and tests as documented before editing anything, and reads stack traces rather than pattern-matching.
- **Root-cause isolation**: distinguishes the config/load-time failure from the three business-logic bugs and fixes them in a sane order.
- **Correct expiry logic**: a verify path that treats missing code, mismatched code, and expired code as distinct cases, with consistent units and timezone handling — not an off-by-one or always-pass check.
- **Real persistence**: confirms the new password is actually written through the data layer, not just returned.
- **Minimal, targeted edits**: surgical fixes that don't churn unrelated code.
- **Verification loop**: re-runs the affected test, then the full suite, plus a manual end-to-end walkthrough (request → verify within 30s → reset → profile updated).
- **Production awareness**: can name how the toy fixes would change for a real system, kept separate from the interview-scope fix.
### Follow-up Questions
- How would you change verification-code generation for production rather than an OA (entropy, format, brute-force resistance, hashing the code at rest)?
- The 30-second window relies on comparing timestamps — what failure modes appear under **clock skew** between servers, or if `generated_at` is stored in a different timezone than `now`?
- How would you test the expiry boundary **deterministically** without a real `sleep(30)` in the suite, and how would you guarantee a used or expired code can't be **replayed**?
- If the suite passed locally but failed in CI, what environment differences (env vars, system time, DB state, base-URL config) would you investigate first?
Quick Answer: This question evaluates debugging and software engineering fundamentals around authentication flows, including verification code generation and expiry logic, persistence of user state, and diagnosing test-harness and configuration import failures.