62 lines
1.9 KiB
Python
62 lines
1.9 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
Re-encrypt existing plaintext TOTP secrets with AES-256-GCM.
|
|
|
|
Run ONCE after applying migration a1b2c3d4e5f6 and before restarting the app:
|
|
|
|
python scripts/reencrypt_totp_secrets.py
|
|
|
|
Requirements:
|
|
- TOTP_ENCRYPTION_KEY must be set in .env (64-char hex string)
|
|
- Run from the project root directory
|
|
|
|
This script is idempotent: it skips users who already have a totp_iv set,
|
|
so it is safe to re-run if interrupted.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
# Allow running from project root
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
from app import create_app, db
|
|
from app.models.user import User
|
|
from app.services.auth_service import encrypt_totp_secret
|
|
|
|
app = create_app(os.environ.get('FLASK_ENV', 'production'))
|
|
|
|
with app.app_context():
|
|
users = User.query.filter(
|
|
User.totp_enabled == True,
|
|
User.totp_secret != None,
|
|
User.totp_iv == None, # skip already-encrypted rows
|
|
).all()
|
|
|
|
if not users:
|
|
print('No plaintext TOTP secrets found. Nothing to do.')
|
|
sys.exit(0)
|
|
|
|
print(f'Found {len(users)} user(s) with plaintext TOTP secrets. Re-encrypting...')
|
|
errors = 0
|
|
for user in users:
|
|
try:
|
|
plaintext = user.totp_secret
|
|
ciphertext_b64, iv_b64 = encrypt_totp_secret(plaintext)
|
|
user.totp_secret = ciphertext_b64
|
|
user.totp_iv = iv_b64
|
|
print(f' [OK] user_id={user.id} ({user.email})')
|
|
except Exception as e:
|
|
print(f' [ERROR] user_id={user.id} ({user.email}): {e}', file=sys.stderr)
|
|
errors += 1
|
|
|
|
if errors:
|
|
db.session.rollback()
|
|
print(f'\nAborted — {errors} error(s) encountered. No changes committed.', file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
db.session.commit()
|
|
print(f'\nDone. {len(users)} TOTP secret(s) re-encrypted successfully.')
|