120 lines
4.0 KiB
Python
Executable File
120 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Security Migration Script
|
|
Migrates existing SHA-256 passwords to bcrypt
|
|
MUST be run after upgrading to bcrypt-based authentication
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
from postgresql_models import SessionLocal, User
|
|
import bcrypt
|
|
import logging
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def migrate_passwords():
|
|
"""
|
|
Migrate all SHA-256 passwords to bcrypt
|
|
This should be run ONCE after upgrading to bcrypt
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
users = db.query(User).all()
|
|
logger.info(f"Found {len(users)} users to check")
|
|
|
|
migrated = 0
|
|
for user in users:
|
|
# Check if password is already bcrypt (starts with $2b$)
|
|
if user.password_hash.startswith('$2b$') or user.password_hash.startswith('$2a$'):
|
|
logger.info(f"User {user.username} already using bcrypt - skipping")
|
|
continue
|
|
|
|
# Check if this is a SHA-256 hash (64 hex characters)
|
|
if len(user.password_hash) == 64 and all(c in '0123456789abcdef' for c in user.password_hash):
|
|
logger.warning(f"User {user.username} has SHA-256 hash - CANNOT migrate automatically")
|
|
logger.warning(f" User must reset password or admin must set new password")
|
|
logger.warning(f" To set new password: user.set_password('new_password')")
|
|
else:
|
|
logger.warning(f"User {user.username} has unknown hash format: {user.password_hash[:20]}...")
|
|
|
|
logger.info(f"Migration complete. {migrated} passwords migrated.")
|
|
logger.warning("")
|
|
logger.warning("IMPORTANT: Users with SHA-256 passwords must reset their passwords!")
|
|
logger.warning(" Option 1: Users can request password reset")
|
|
logger.warning(" Option 2: Admin can manually set new passwords using user.set_password()")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Migration failed: {e}")
|
|
db.rollback()
|
|
return 1
|
|
finally:
|
|
db.close()
|
|
|
|
return 0
|
|
|
|
def create_default_admin(username='admin', password=None):
|
|
"""
|
|
Create a default admin user with bcrypt password
|
|
Use this to create the first admin account
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
# Check if user exists
|
|
existing = db.query(User).filter(User.username == username).first()
|
|
if existing:
|
|
logger.error(f"User {username} already exists")
|
|
return 1
|
|
|
|
if not password:
|
|
logger.error("Password is required")
|
|
return 1
|
|
|
|
# Create new admin user
|
|
admin = User(
|
|
username=username,
|
|
role='admin',
|
|
permissions='view,edit,modify,settings',
|
|
active=True
|
|
)
|
|
admin.set_password(password)
|
|
|
|
db.add(admin)
|
|
db.commit()
|
|
|
|
logger.info(f"✓ Admin user '{username}' created successfully with bcrypt password")
|
|
return 0
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to create admin: {e}")
|
|
db.rollback()
|
|
return 1
|
|
finally:
|
|
db.close()
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Security migration tool')
|
|
parser.add_argument('--migrate', action='store_true', help='Migrate SHA-256 passwords to bcrypt')
|
|
parser.add_argument('--create-admin', action='store_true', help='Create default admin user')
|
|
parser.add_argument('--username', default='admin', help='Username for new admin')
|
|
parser.add_argument('--password', help='Password for new admin')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.create_admin:
|
|
if not args.password:
|
|
print("ERROR: --password required when creating admin")
|
|
sys.exit(1)
|
|
sys.exit(create_default_admin(args.username, args.password))
|
|
|
|
if args.migrate:
|
|
sys.exit(migrate_passwords())
|
|
|
|
parser.print_help()
|
|
sys.exit(1)
|