forked from MoyuZJ912/iFlyCompass
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
196 lines (164 loc) · 6.23 KB
/
Copy pathconfig.py
File metadata and controls
196 lines (164 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import os
import secrets
import hashlib
import base64
import yaml
from datetime import timedelta
from cryptography.fernet import Fernet
CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'instance', 'config.yml')
# ---- Encryption utilities for sensitive config values ----
_fernet = None
def _get_fernet():
"""Get or create a Fernet instance derived from the Flask SECRET_KEY."""
global _fernet
if _fernet is not None:
return _fernet
# Derive a 32-byte key from SECRET_KEY, then base64-urlsafe encode for Fernet
secret = Config.SECRET_KEY
if not secret:
secret = secrets.token_hex(32)
Config.SECRET_KEY = secret
# Use SHA-256 to get consistent 32 bytes, then base64 encode
derived = hashlib.sha256(secret.encode()).digest()
fernet_key = base64.urlsafe_b64encode(derived)
_fernet = Fernet(fernet_key)
return _fernet
def _is_encrypted(value):
"""Check if a value is already encrypted (prefixed with 'enc:')."""
return isinstance(value, str) and value.startswith('enc:')
def encrypt_value(value):
"""Encrypt a string value. Returns 'enc:<base64>' prefixed string."""
if not value:
return value
f = _get_fernet()
encrypted = f.encrypt(value.encode())
return 'enc:' + base64.urlsafe_b64encode(encrypted).decode()
def decrypt_value(value):
"""Decrypt an 'enc:<base64>' prefixed string. Returns plaintext.
If value is not encrypted (no 'enc:' prefix), returns as-is for backward compatibility."""
if not value:
return value
if not _is_encrypted(value):
# Plaintext value — migrate: encrypt it on next save
return value
try:
f = _get_fernet()
encrypted = base64.urlsafe_b64decode(value[4:].encode())
return f.decrypt(encrypted).decode()
except Exception:
# If decryption fails (e.g., secret key changed), return empty
return ''
DEFAULT_CONFIG = {
'flask': {
'secret_key': None,
'session_lifetime_days': 3650
},
'database': {
'path': 'users.db'
},
'directories': {
'temp': './temp',
'assets': './assets',
'stickers': './stickers',
'novels': './instance/novels',
'music_cache': './temp/music',
'videos': './instance/videos'
},
'external_api': {
'sticker_api': 'http://45.207.204.145:5003/api'
},
'chat': {
'max_message_history': 20
},
'system_settings': {
'home_display': 'nickname',
'allow_nickname': True,
'nickname_min_length': 2,
'nickname_max_length': 20,
'sidebar_default_expanded': False,
'card_layout': '1x4',
'username_manual_min': 3,
'username_manual_max': 50,
'username_register_min': 3,
'username_register_max': 50,
'password_strength': 1,
'allow_weak_password': False,
'allow_self_password_reset': False,
'allow_change_password': True
}
}
_config_cache = None
def _ensure_instance_dir():
instance_dir = os.path.dirname(CONFIG_FILE)
if not os.path.exists(instance_dir):
os.makedirs(instance_dir)
def _load_config():
global _config_cache
if _config_cache is not None:
return _config_cache
_ensure_instance_dir()
if not os.path.exists(CONFIG_FILE):
print("配置文件不存在,创建默认配置...")
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
yaml.dump(DEFAULT_CONFIG, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
_config_cache = DEFAULT_CONFIG.copy()
return _config_cache
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
for key, value in DEFAULT_CONFIG.items():
if key not in config:
config[key] = value
elif isinstance(value, dict):
for sub_key, sub_value in value.items():
if sub_key not in config[key]:
config[key][sub_key] = sub_value
_config_cache = config
return _config_cache
def get_config():
return _load_config()
def save_config(config):
global _config_cache
_ensure_instance_dir()
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
yaml.dump(config, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
_config_cache = config
def _init_config():
config = get_config()
flask_config = config.get('flask', {})
db_config = config.get('database', {})
dirs = config.get('directories', {})
external = config.get('external_api', {})
chat_config = config.get('chat', {})
Config.SECRET_KEY = flask_config.get('secret_key') or os.environ.get('SECRET_KEY') or secrets.token_hex(32)
# Persist auto-generated secret_key so encrypted values survive restarts
if not flask_config.get('secret_key') and not os.environ.get('SECRET_KEY'):
config['flask']['secret_key'] = Config.SECRET_KEY
save_config(config)
Config.PERMANENT_SESSION_LIFETIME = timedelta(days=flask_config.get('session_lifetime_days', 3650))
Config.INSTANCE_DIR = os.path.join(os.path.dirname(__file__), 'instance')
db_path = os.path.join(Config.INSTANCE_DIR, db_config.get('path', 'users.db'))
Config.SQLALCHEMY_DATABASE_URI = f'sqlite:///{db_path}'
Config.SQLALCHEMY_TRACK_MODIFICATIONS = False
Config.TEMP_DIR = dirs.get('temp', './temp')
Config.ASSETS_DIR = dirs.get('assets', './assets')
Config.STICKERS_DIR = dirs.get('stickers', './stickers')
Config.NOVELS_DIR = dirs.get('novels', './instance/novels')
Config.MUSIC_CACHE_DIR = dirs.get('music_cache', './temp/music')
Config.VIDEOS_DIR = dirs.get('videos', './instance/videos')
Config.STICKER_API_BASE = external.get('sticker_api', 'http://45.207.204.145:5003/api')
Config.MAX_MESSAGE_HISTORY = chat_config.get('max_message_history', 20)
class Config:
SECRET_KEY = None
PERMANENT_SESSION_LIFETIME = None
INSTANCE_DIR = None
SQLALCHEMY_DATABASE_URI = None
SQLALCHEMY_TRACK_MODIFICATIONS = False
TEMP_DIR = None
ASSETS_DIR = None
STICKERS_DIR = None
NOVELS_DIR = None
MUSIC_CACHE_DIR = None
VIDEOS_DIR = None
STICKER_API_BASE = None
MAX_MESSAGE_HISTORY = None
_init_config()