"""Add trusted_devices table (login-verification device recognition)

Stores browsers a user has chosen to trust so the emailed login-OTP step is
skipped for a configured window. The raw device token is never stored — only a
salted SHA-256 hash of it (the token itself lives solely in a secure HttpOnly
cookie). Raw IP / user-agent are likewise stored only as salted hashes. This is
the storage anticipated by Security_Design §19 ("Device Recognition"); it changes
no existing table.

Revision ID: d1f3a5c7e9b2
Revises: c6e8a0b2d4f3
Create Date: 2026-07-06 00:00:00.000000

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "d1f3a5c7e9b2"
down_revision = "c6e8a0b2d4f3"
branch_labels = None
depends_on = None


def upgrade():
    op.create_table(
        "trusted_devices",
        sa.Column("user_id", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), nullable=False),
        sa.Column("token_hash", sa.CHAR(length=64), nullable=False),
        sa.Column("device_label", sa.String(length=120), nullable=True),
        sa.Column("ip_hash", sa.CHAR(length=64), nullable=True),
        sa.Column("user_agent_hash", sa.CHAR(length=64), nullable=True),
        sa.Column("last_used_at", sa.DateTime(), nullable=False),
        sa.Column("expires_at", sa.DateTime(), nullable=False),
        sa.Column("revoked_at", sa.DateTime(), nullable=True),
        sa.Column("created_at", sa.DateTime(), nullable=False),
        sa.Column("id", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), autoincrement=True, nullable=False),
        sa.ForeignKeyConstraint(["user_id"], ["users.id"], name="fk_trusted_devices_user_id"),
        sa.PrimaryKeyConstraint("id"),
    )
    with op.batch_alter_table("trusted_devices", schema=None) as batch_op:
        batch_op.create_index("ix_trusted_devices_user_id", ["user_id"], unique=False)
        batch_op.create_index("ix_trusted_devices_token_hash", ["token_hash"], unique=False)
        batch_op.create_index("ix_trusted_devices_expires_at", ["expires_at"], unique=False)


def downgrade():
    op.drop_table("trusted_devices")
