diff --git a/README.md b/README.md index 949398d..f006701 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ curl -Ls https://raw.githubusercontent.com/grendervilll/mitactl/main/bootstrap.s - [x] Интеграция Cloudflare WARP через Docker - [x] Веб-панель с секретным путём (Flask + Gunicorn, path-based security) - [x] Тёмная тема, адаптивный UI: Dashboard, Users, SSL, Security -- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk +- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk, выбор сложности пароля - [x] Статистика трафика: день, 7 дней, 30 дней (позиционный парсинг mita get users) - [x] Онлайн-статус пользователей - [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON) diff --git a/app.py b/app.py index a4a3dca..e72e713 100644 --- a/app.py +++ b/app.py @@ -141,10 +141,11 @@ def mita_cmd(*args): r = subprocess.run(["mita", *args], capture_output=True, text=True) return r.stdout.strip() -def gen_password(length=64): - # Буквы и цифры только — спецсимволы могут ломать mita protobuf парсинг - # Используем расширенный безопасный набор без проблемных символов - chars = string.ascii_letters + string.digits + "!@#%^*_-=+?." +def gen_password(length=64, mode="hard"): + if mode == "easy": + chars = string.ascii_letters + string.digits + "-._~*+" + else: + chars = string.ascii_letters + string.digits + "!@#%^*_-=+?." return "".join(secrets.choice(chars) for _ in range(length)) def gen_username(): @@ -509,6 +510,7 @@ def api_users_create(): count = int(data.get("count", 1)) mode = data.get("mode", "manual") # manual | auto names = data.get("names", []) # для manual + pwd_mode = data.get("password_mode", "hard") # easy | hard cfg = load_mita_config() existing = {u["name"] for u in cfg.get("users", [])} @@ -527,7 +529,7 @@ def api_users_create(): if name in existing: continue - password = gen_password() + password = gen_password(mode=pwd_mode) cfg.setdefault("users", []).append({"name": name, "password": password}) existing.add(name) created.append({ diff --git a/bot.py b/bot.py index 565a1d2..d2bbbe2 100644 --- a/bot.py +++ b/bot.py @@ -82,8 +82,11 @@ def _mita_running(): except Exception: return False -def gen_password(length=64): - chars = string.ascii_letters + string.digits + "!@#%^*_-=+?." +def gen_password(length=64, mode="hard"): + if mode == "easy": + chars = string.ascii_letters + string.digits + "-._~*+" + else: + chars = string.ascii_letters + string.digits + "!@#%^*_-=+?." return "".join(secrets.choice(chars) for _ in range(length)) def gen_username(): @@ -222,13 +225,28 @@ async def show_users(update: Update, context: ContextTypes.DEFAULT_TYPE): async def create_user(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() + await query.edit_message_text( + "➕ *Создать пользователя*\n\nВыберите сложность пароля:", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("🔓 Лёгкий (A-Z, 0-9, -._~*+)", callback_data="create_easy")], + [InlineKeyboardButton("🔐 Сложный (со спецсимволами)", callback_data="create_hard")], + [InlineKeyboardButton("« Назад", callback_data="main")], + ]), + parse_mode="MarkdownV2" + ) + +@admin_only +async def create_exec(update: Update, context: ContextTypes.DEFAULT_TYPE): + query = update.callback_query + await query.answer() + pwd_mode = "easy" if query.data == "create_easy" else "hard" cfg = load_mita_config() existing = {u["name"] for u in cfg.get("users", [])} name = gen_username() while name in existing: name = gen_username() - password = gen_password() + password = gen_password(mode=pwd_mode) cfg.setdefault("users", []).append({"name": name, "password": password}) Path(MITA_CONFIG).write_text(json.dumps(cfg, indent=2, ensure_ascii=False)) @@ -625,6 +643,8 @@ async def router(update: Update, context: ContextTypes.DEFAULT_TYPE): return await show_users(update, context) elif data == "create": return await create_user(update, context) + elif data in ("create_easy", "create_hard"): + return await create_exec(update, context) elif data == "delete_menu": return await delete_menu(update, context) elif data.startswith("delete_"): diff --git a/mita-ctl.sh b/mita-ctl.sh index 3bad264..1a9e989 100755 --- a/mita-ctl.sh +++ b/mita-ctl.sh @@ -530,11 +530,23 @@ for u in users: print(f' - {u[\"name\"]}') echo "" read -r -p " Выбор: " choice + # Запрашиваем сложность пароля один раз перед созданием + local pwd_mode="hard" + if [[ "$choice" == "1" || "$choice" == "2" ]]; then + echo "" + echo -e " ${BOLD}Сложность пароля:${NC}" + echo -e " ${BOLD}1.${NC} Лёгкий — A-Z, a-z, 0-9, -._~*+ (без спецсимволов)" + echo -e " ${BOLD}2.${NC} Сложный — со спецсимволами (по умолчанию)" + echo "" + read -r -p " Выбор [2]: " pwd_choice + [[ "$pwd_choice" == "1" ]] && pwd_mode="easy" + fi + case "$choice" in 1) read -r -p "Имя пользователя: " uname [[ -z "$uname" ]] && { warn "Имя не указано"; pause; return; } - _create_user "$uname" + _create_user "$uname" "$pwd_mode" pause ;; 2) read -r -p "Количество пользователей: " cnt @@ -545,7 +557,7 @@ for u in users: print(f' - {u[\"name\"]}') noun=$(shuf -n1 -e fox hawk river storm ember peak orbit tide frost spark 2>/dev/null || echo "node") rnd=$((RANDOM % 9000 + 1000)) uname="${adj}_${noun}_${rnd}" - _create_user "$uname" + _create_user "$uname" "$pwd_mode" done pause ;; 3) @@ -589,8 +601,13 @@ PYEOF _create_user() { local uname="$1" + local pwd_mode="$2" local pass - pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48) + if [[ "$pwd_mode" == "easy" ]]; then + pass=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9-._~*+' | head -c 32) + else + pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48) + fi # Записываем пользователя в JSON и проверяем результат явно local py_result diff --git a/templates/index.html b/templates/index.html index 07967c8..17689b6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -95,6 +95,13 @@ +
+ + +