Make a recovery credential single-use

A recovery link can be redeemed twice when two requests arrive together. Repair redemption so token validation and account changes share one atomic operation.

Requires Node.js 22 or newer. No package installation or server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit recovery.mjs, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same cases.mjs with the same fixtures; locally Node runs your code directly. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. Reproduce the double redemption
Run concurrent redemption. Both starter requests see an unused token before either marks it used. Inspect the number of credential changes.

2. Use the complete transaction
Call await service.redeem(request.token, request.account). Convert true to changed and false to rejected. The supplied operation validates the token and performs the effects as one transaction.

3. Preserve account binding
Pass the requested account unchanged to the service. A token for Alice must not change Bob’s account; an enrollment token cannot become a recovery token.

4. Check invalid and normal cases
Expired, unknown, wrong-purpose and already used tokens must reject. The first valid recovery still changes the account and revokes existing sessions.

SUPPLIED CONTRACT
recoverAccount(request, service) returns changed or rejected. request has token and account. service.redeem(token, account) atomically checks account, purpose, expiration and unused state, updates the credential and revokes old sessions, returning true or false. service.peek and service.changeUnsafe expose the flawed alternative for the starter. Fixture time is fixed.

REFERENCE
reference.mjs contains an explained solution: The unsafe flow allows both concurrent callers to observe used=false before either writes. Atomic redeem marks the token used while validating account=alice, purpose=recovery and expires>100, then records one credential change and one session revocation. The sorted concurrent answers are changed/rejected; they do not identify which request won. Expiry exactly 100 fails. Passing bob instead of alice must reject with zero changes and zero revocations. The harness counts these effects; it does not handle or store a new real credential.

SCOPE
This is a transaction-contract exercise, not a complete recovery service. Production needs high-entropy credentials, hashed storage, bounded attempts, delivery-channel protection, secure new-credential handling, notifications, auditing and durable rollback semantics.

NEXT TASK
What happens if your recovery process commits the credential change but crashes before invalidating the old session?

SOURCE
https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html
