version 1.3.3: выбор сложности пароля (лёгкий/сложный)

- app.py: gen_password(mode), api_users_create принимает password_mode
- bot.py: меню выбора сложности перед созданием, gen_password(mode)
- mita-ctl.sh: запрос сложности в menu_users, _create_user с pwd_mode
- templates/index.html: селектор «Сложность пароля» в форме создания
- README: обновлена дорожная карта
This commit is contained in:
2026-06-28 15:11:17 +03:00
parent 6e74b9e539
commit e75e5ee211
5 changed files with 63 additions and 16 deletions
+1 -1
View File
@@ -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)
+7 -5
View File
@@ -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({
+23 -3
View File
@@ -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_"):
+20 -3
View File
@@ -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
+12 -4
View File
@@ -95,6 +95,13 @@
<label>Имя пользователя</label>
<input type="text" id="create-name" placeholder="например: vasya">
</div>
<div class="form-group" style="max-width:160px">
<label>Сложность пароля</label>
<select id="create-pwd-mode">
<option value="hard">Сложный</option>
<option value="easy">Лёгкий</option>
</select>
</div>
<div>
<button class="btn btn-primary" onclick="createUsers()">
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/></svg>
@@ -490,17 +497,18 @@ toggleCreateMode();
async function createUsers(){
const mode=document.getElementById('create-mode').value;
let body={};
const pwdMode=document.getElementById('create-pwd-mode').value;
let body={password_mode:pwdMode};
if(mode==='auto'){
body={mode:'auto',count:parseInt(document.getElementById('create-count').value)||1};
body={...body,mode:'auto',count:parseInt(document.getElementById('create-count').value)||1};
}else if(mode==='manual'){
const n=document.getElementById('create-name').value.trim();
if(!n){showToast('Введите имя',true);return;}
body={mode:'manual',names:[n],count:1};
body={...body,mode:'manual',names:[n],count:1};
}else{
const lines=document.getElementById('bulk-names').value.split('\n').map(s=>s.trim()).filter(Boolean);
if(!lines.length){showToast('Введите имена',true);return;}
body={mode:'manual',names:lines,count:lines.length};
body={...body,mode:'manual',names:lines,count:lines.length};
}
const d=await api('/api/users/create','POST',body);
if(!d||!d.created){showToast('Ошибка',true);return;}