dak.c2s/backend/alembic/versions/008_password_reset_tokens.py
CCS Admin d5db84d93f feat: add self-service password reset via email
Adds "Passwort vergessen?" to login page with email-based password
reset flow. Backend generates secure token (SHA-256 hashed, 1h expiry),
sends reset link via SMTP, and validates on submission. Includes rate
limiting (3 requests/hour/email), audit logging, and account unlock
on successful reset. New ResetPasswordPage with password confirmation.

New DB table: password_reset_tokens (migration 008).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:56:07 +00:00

43 lines
1.3 KiB
Python

"""Add password_reset_tokens table.
Revision ID: 008_password_reset_tokens
Revises: 007_add_report_type
"""
from alembic import op
import sqlalchemy as sa
revision = "008_password_reset_tokens"
down_revision = "007_add_report_type"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"password_reset_tokens",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column(
"user_id",
sa.Integer,
sa.ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("token_hash", sa.String(255), nullable=False),
sa.Column("expires_at", sa.DateTime, nullable=False),
sa.Column("used_at", sa.DateTime, nullable=True),
sa.Column(
"created_at",
sa.DateTime,
nullable=False,
server_default=sa.func.now(),
),
)
op.create_index("idx_prt_token", "password_reset_tokens", ["token_hash"])
op.create_index("idx_prt_user", "password_reset_tokens", ["user_id"])
def downgrade() -> None:
op.drop_index("idx_prt_user", table_name="password_reset_tokens")
op.drop_index("idx_prt_token", table_name="password_reset_tokens")
op.drop_table("password_reset_tokens")