from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from cryptography.fernet import Fernet import os import json import logging logger = logging.getLogger(__name__) # 1. Инициализируем расширение без привязки к app from extensions import db # 2. Инициализация Fernet вынесена в функцию, чтобы она вызывалась ПОСЛЕ загрузки .env fernet = None def init_encryption(app): """Инициализирует Fernet после того, как конфигурация загружена.""" global fernet encryption_key_str = app.config.get('ENCRYPTION_KEY') if not encryption_key_str: logger.error("Переменная окружения ENCRYPTION_KEY не установлена! Шифрование паролей RMS не будет работать.") logger.warning("Генерируется временный ключ шифрования. Для продакшена ОБЯЗАТЕЛЬНО установите ENCRYPTION_KEY!") encryption_key = Fernet.generate_key() else: try: encryption_key = encryption_key_str.encode('utf-8') Fernet(encryption_key) logger.info("Ключ шифрования ENCRYPTION_KEY успешно загружен.") except Exception as e: logger.critical(f"Недопустимый формат ключа ENCRYPTION_KEY: {e}") raise ValueError("Недопустимый формат ключа ENCRYPTION_KEY.") from e fernet = Fernet(encryption_key) class User(db.Model, UserMixin): # ... код класса User остается без изменений ... id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) password_hash = db.Column(db.String(256)) config = db.relationship('UserConfig', backref='user', uselist=False, cascade="all, delete-orphan") def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return f'' class UserConfig(db.Model): # ... код класса UserConfig почти без изменений ... id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True) rms_host = db.Column(db.String(200)) rms_login = db.Column(db.String(100)) rms_password_encrypted = db.Column(db.LargeBinary) google_cred_file_path = db.Column(db.String(300)) google_sheet_url = db.Column(db.String(300)) google_client_email = db.Column(db.String(200)) presets_json = db.Column(db.Text, default='[]') sheets_json = db.Column(db.Text, default='[]') mappings_json = db.Column(db.Text, default='{}') @property def rms_password(self): """Дешифрует пароль RMS при доступе.""" if not fernet: raise RuntimeError("Fernet encryption is not initialized. Call init_encryption(app) first.") if self.rms_password_encrypted: try: return fernet.decrypt(self.rms_password_encrypted).decode('utf-8') except Exception as e: logger.error(f"Ошибка дешифрования пароля для user_id {self.user_id}: {e}") return None return None @rms_password.setter def rms_password(self, value): """Шифрует пароль RMS при установке.""" if not fernet: raise RuntimeError("Fernet encryption is not initialized. Call init_encryption(app) first.") if value: self.rms_password_encrypted = fernet.encrypt(value.encode('utf-8')) else: self.rms_password_encrypted = None # ... остальные properties (presets, sheets, mappings) и методы (get_rms_dict, get_google_dict) остаются без изменений ... @property def presets(self): return json.loads(self.presets_json or '[]') @presets.setter def presets(self, value): self.presets_json = json.dumps(value or [], ensure_ascii=False) @property def sheets(self): return json.loads(self.sheets_json or '[]') @sheets.setter def sheets(self, value): self.sheets_json = json.dumps(value or [], ensure_ascii=False) @property def mappings(self): return json.loads(self.mappings_json or '{}') @mappings.setter def mappings(self, value): self.mappings_json = json.dumps(value or {}, ensure_ascii=False) def get_rms_dict(self): return { 'host': self.rms_host or '', 'login': self.rms_login or '', 'password_is_set': bool(self.rms_password_encrypted) } def get_google_dict(self): return { 'cred_file_is_set': bool(self.google_cred_file_path and os.path.exists(self.google_cred_file_path)), 'sheet_url': self.google_sheet_url or '' } def __repr__(self): return f''