50 lines
1.5 KiB
Python
50 lines
1.5 KiB
Python
"""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,
|
|
)
|