05/18 Enhanced codes and functionalities 3
This commit is contained in:
+19
-7
@@ -922,8 +922,11 @@ def recovery_items():
|
||||
the client must derive it by decrypting the recovery blob with the recovery
|
||||
code. This ensures only the holder of the recovery code can compute the proof.
|
||||
|
||||
The challenge row is NOT consumed here — it is consumed by the final
|
||||
POST /recover call so that endpoint can also validate the proof.
|
||||
Replay prevention: the challenge is consumed (deleted) on success, then
|
||||
immediately re-issued with the same expected_proof but a new nonce and a
|
||||
fresh TTL. This means each call to /recovery/items rotates the challenge,
|
||||
so a captured X-Recovery-Proof header cannot be replayed by a third party.
|
||||
POST /recover will consume the rotated challenge on final commit.
|
||||
Items are returned as encrypted ciphertext blobs only.
|
||||
"""
|
||||
from app.models.recovery_challenge import RecoveryChallenge
|
||||
@@ -938,11 +941,9 @@ def recovery_items():
|
||||
if not user or not user.recovery_enc_salt:
|
||||
return jsonify({'error': 'No recovery data found'}), 404
|
||||
|
||||
# Validate against the DB-stored challenge without consuming it —
|
||||
# POST /recover will consume it atomically on commit.
|
||||
challenge = RecoveryChallenge.query.filter_by(user_id=user.id).first()
|
||||
from datetime import datetime, timezone
|
||||
if not challenge or challenge.expires_at < datetime.now(timezone.utc).replace(tzinfo=None):
|
||||
# Consume the current challenge atomically.
|
||||
challenge = RecoveryChallenge.consume(user.id)
|
||||
if not challenge:
|
||||
return jsonify({'error': 'No active recovery challenge. Call /recovery/data first.'}), 400
|
||||
|
||||
if not verify_recovery_proof(challenge.expected_proof, client_proof):
|
||||
@@ -957,6 +958,17 @@ def recovery_items():
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Invalid recovery proof'}), 401
|
||||
|
||||
# Re-issue a fresh challenge with the same expected_proof but a new nonce
|
||||
# and TTL. POST /recover will consume this rotated challenge on final commit.
|
||||
# The client continues to send the same proof value — no client change needed.
|
||||
new_nonce = generate_recovery_nonce()
|
||||
RecoveryChallenge.create(
|
||||
user_id=user.id,
|
||||
nonce=new_nonce,
|
||||
expected_proof=challenge.expected_proof, # same proof, new nonce
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
from app.models.vault_item import VaultItem
|
||||
items = VaultItem.query.filter_by(user_id=user.id).all()
|
||||
return jsonify({
|
||||
|
||||
Reference in New Issue
Block a user