feat: email verification, merchant/store password reset, seed gap fix
Some checks failed
Some checks failed
- Add EmailVerificationToken and UserPasswordResetToken models with migration - Add email verification flow: verify-email page route, resend-verification API - Block login for unverified users (EmailNotVerifiedException in auth_service) - Add forgot-password/reset-password endpoints for merchant and store auth - Add "Forgot Password?" links to merchant and store login pages - Send welcome email with verification link on merchant creation - Seed email_verification and merchant_password_reset email templates - Fix db-reset Makefile to run all init-prod seed scripts - Add UserAuthService to satisfy architecture validation rules - Add 52 new tests (unit + integration) with full coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""tenancy: add email_verification_tokens and user_password_reset_tokens tables
|
||||
|
||||
Revision ID: tenancy_002
|
||||
Revises: tenancy_001
|
||||
Create Date: 2026-02-18
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "tenancy_002"
|
||||
down_revision = "a44f4956cfb1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# --- email_verification_tokens ---
|
||||
op.create_table(
|
||||
"email_verification_tokens",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
||||
sa.Column(
|
||||
"user_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("token_hash", sa.String(64), nullable=False, index=True),
|
||||
sa.Column("expires_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("used_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
|
||||
# --- user_password_reset_tokens ---
|
||||
op.create_table(
|
||||
"user_password_reset_tokens",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, index=True),
|
||||
sa.Column(
|
||||
"user_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("token_hash", sa.String(64), nullable=False, index=True),
|
||||
sa.Column("expires_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("used_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("user_password_reset_tokens")
|
||||
op.drop_table("email_verification_tokens")
|
||||
Reference in New Issue
Block a user