Password Reset Blueprint
Allow users to reset their password via email verification
| Feature | password-reset |
| Category | Auth |
| Version | 1.0.0 |
| Tags | password, reset, recovery, security, email |
| YAML Source | View on GitHub |
| JSON API | password-reset.json |
Fields
| Name | Type | Required | Label | Description |
|---|---|---|---|---|
email | Yes | Email Address | Validations: required, email, maxLength | |
token | token | Yes | Validations: required | |
new_password | password | Yes | New Password | Validations: required, minLength, maxLength, pattern |
confirm_new_password | password | Yes | Confirm New Password | Validations: required, match |
Rules
- security:
- token:
- type: cryptographic_random
- length_bytes: 32
- hash_before_storage: true
- algorithm: sha256
- token_expiry:
- minutes: 60
- single_use: true
- invalidate_previous: true
- rate_limit:
- window_seconds: 3600
- max_requests: 3
- scope: per_email
- rate_limit_global:
- window_seconds: 3600
- max_requests: 20
- scope: per_ip
- password_history:
- check_previous: false
- count: 0
- invalidate_sessions_on_reset: true
- token:
- email:
- case_sensitive: false
- trim_whitespace: true
- enumeration_prevention: true
- password_comparison:
- constant_time: true
Flows
Request_happy_path
User requests a password reset link
- validate_fields — Check email format
- normalize_email — Lowercase and trim
- lookup_user_by_email — Find user in database
- check_rate_limit — Verify not too many requests
- invalidate_previous_tokens — Cancel any existing reset tokens
- generate_reset_token — Create crypto random token, store hash in DB
- send_reset_email — Email the reset link with token
- emit
- show_message
Request_email_not_found
Email not in system — but we don’t reveal that
- emit
- show_message
Reset_happy_path
User submits new password with valid token
- validate_fields — Check password requirements and match
- validate_reset_token — Hash the URL token, look up in DB, check expiry
- check_password_history — Optional: ensure new password differs from recent ones
- hash_new_password — bcrypt.hash(new_password, salt_rounds)
- update_user_password — Save new hash to database
- invalidate_reset_token — Mark token as used — single use
- invalidate_all_sessions — Log out all active sessions for this user
- emit
- send_confirmation_email — Notify user their password was changed
- redirect
Token_invalid
Token expired, already used, or tampered with
- emit
- show_error
- show_link
Token_expired
Token is valid but past expiry
- emit
- show_error
- show_link
Password_reused
New password matches a recent password
- show_error
Rate_limited
Too many reset requests
- show_message
Outcomes
Request_rate_limited (Priority: 1) — Error: RESET_RATE_LIMITED
Given:
- ANY:
email_request_count(computed) gt3ORip_request_count(computed) gt20
Result: show “If an account with that email exists, we’ve sent a password reset link.” (same as success — don’t reveal rate limiting)
Reset_requested_unknown_email (Priority: 2)
Given:
user(db) not_exists
Then:
- emit_event event:
password_reset.email_not_found
Result: show SAME message as successful request (enumeration prevention)
Reset_requested (Priority: 3) | Transaction: atomic
Given:
email(input) matches^[^\s@]+@[^\s@]+\.[^\s@]+$user(db) exists
Then:
- invalidate target:
reset_tokens— Invalidate all previous reset tokens - create_record target:
reset_token— Generate crypto.randomBytes(32), store SHA-256 hash, expires in 60 min - notify to:
user— Send reset link with raw token - emit_event event:
password_reset.requested
Result: show “If an account with that email exists, we’ve sent a password reset link.”
Token_invalid (Priority: 4) — Error: RESET_TOKEN_INVALID
Given:
- ANY:
token_hash(db) not_exists ORtoken_used(db) eqtrue
Then:
- emit_event event:
password_reset.token_invalid
Result: show “This reset link is invalid. Please request a new one.” with link to /forgot-password
Token_expired (Priority: 5) — Error: RESET_TOKEN_EXPIRED
Given:
token_hash(db) existstoken_created_at(db) ltnow - 60 minutes
Then:
- emit_event event:
password_reset.token_expired
Result: show “This reset link has expired. Please request a new one.” with link to /forgot-password
Password_reused (Priority: 6) — Error: RESET_PASSWORD_REUSED
Given:
new_password(computed) inrecent_password_hashes
Result: show “Please choose a password you haven’t used recently”
Password_reset_success (Priority: 10) — Error: RESET_PASSWORD_WEAK | Transaction: atomic
Given:
token(input) existstoken_hash(db) existstoken_created_at(db) gtenow - 60 minutestoken_used(db) eqfalsenew_password(input) matches^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,64}$confirm_new_password(input) eqnew_password
Then:
- set_field target:
password_hashvalue:bcrypt(new_password, 12)— Hash new password with bcrypt (12 rounds) - set_field target:
token_usedvalue:true— Mark token as used (single-use) - invalidate target:
sessions— Invalidate ALL active sessions for this user - notify to:
user— Confirm password was changed - emit_event event:
password_reset.success
Result: redirect to /login with “Password reset successful. Please sign in with your new password.”
Errors
| Code | Status | Message | Retry |
|---|---|---|---|
RESET_VALIDATION_ERROR | 422 | Please check your input and try again | Yes |
RESET_TOKEN_INVALID | 400 | This reset link is invalid. Please request a new one. | No |
RESET_TOKEN_EXPIRED | 400 | This reset link has expired. Please request a new one. | No |
RESET_PASSWORD_WEAK | 422 | Password does not meet security requirements | Yes |
RESET_PASSWORD_MISMATCH | 422 | Passwords do not match | Yes |
RESET_PASSWORD_REUSED | 422 | Please choose a password you haven’t used recently | Yes |
RESET_RATE_LIMITED | 429 | Please wait before requesting another reset | No |
Events
| Event | Description | Payload |
|---|---|---|
password_reset.requested | User requested a password reset email | user_id, email, timestamp, ip_address, token_expires_at |
password_reset.success | Password was successfully changed | user_id, email, timestamp, ip_address |
password_reset.token_invalid | Invalid or tampered token was submitted | token_hash, timestamp, ip_address |
password_reset.token_expired | Expired token was submitted | user_id, timestamp, ip_address |
password_reset.email_not_found | Reset requested for non-existent email | email, timestamp, ip_address |
Related Blueprints
| Feature | Relationship | Reason |
|---|---|---|
| login | required | After reset, user logs in with new password |
| signup | recommended | User might not have an account yet |
| email-verification | optional | Uses similar email token pattern — can share infrastructure |
AGI Readiness
Goals
Reliable Password Reset
Allow users to reset their password via email verification
Success Metrics:
| Metric | Target | Measurement |
|---|---|---|
| unauthorized_access_rate | 0% | Failed authorization attempts that succeed |
| response_time_p95 | < 500ms | 95th percentile response time |
Constraints:
- security (non-negotiable): Follow OWASP security recommendations
- security (non-negotiable): Sensitive fields must be encrypted at rest and never logged in plaintext
Autonomy
Level: supervised
Human Checkpoints:
- before modifying sensitive data fields
Escalation Triggers:
error_rate > 5consecutive_failures > 3
Verification
Invariants:
- sensitive fields are never logged in plaintext
- all data access is authenticated and authorized
- error messages never expose internal system details
Tradeoffs
| Prefer | Over | Reason |
|---|---|---|
| security | performance | authentication must prioritize preventing unauthorized access |
Coordination
Protocol: request_response
Consumes:
| Capability | From | Fallback |
|---|---|---|
login | login | fail |
Safety
| Action | Permission | Cooldown | Max Auto |
|---|---|---|---|
| request_rate_limited | autonomous | - | - |
| reset_requested_unknown_email | autonomous | - | - |
| reset_requested | autonomous | - | - |
| token_invalid | autonomous | - | - |
| token_expired | autonomous | - | - |
| password_reused | autonomous | - | - |
| password_reset_success | autonomous | - | - |