"""encrypt totp_secret at rest: widen column and add totp_iv Revision ID: a1b2c3d4e5f6 Revises: 71d7158dd3b9 Create Date: 2026-04-18 00:00:00.000000 This migration: 1. Widens users.totp_secret from VARCHAR(64) to VARCHAR(255) to hold base64 AES-256-GCM ciphertext (plaintext secret ~32 chars → ciphertext ~64 bytes → base64 ~88 chars, plus GCM tag 16 bytes → up to ~120 chars; 255 is safe). 2. Adds users.totp_iv VARCHAR(64) for the base64 12-byte GCM nonce. After running this migration you MUST run the one-time re-encryption script: python scripts/reencrypt_totp_secrets.py That script reads every existing plaintext totp_secret, encrypts it with the TOTP_ENCRYPTION_KEY from .env, and writes back the ciphertext + iv. """ from alembic import op import sqlalchemy as sa revision = 'a1b2c3d4e5f6' down_revision = '71d7158dd3b9' branch_labels = None depends_on = None def upgrade(): with op.batch_alter_table('users', schema=None) as batch_op: batch_op.alter_column( 'totp_secret', existing_type=sa.String(length=64), type_=sa.String(length=255), existing_nullable=True, ) batch_op.add_column(sa.Column('totp_iv', sa.String(length=64), nullable=True)) def downgrade(): with op.batch_alter_table('users', schema=None) as batch_op: batch_op.drop_column('totp_iv') batch_op.alter_column( 'totp_secret', existing_type=sa.String(length=255), type_=sa.String(length=64), existing_nullable=True, )