fix
This commit is contained in:
parent
d60dc1f8c2
commit
b990cd91f0
@ -1797,7 +1797,7 @@
|
||||
" restore_data: RestoreRequest = Body(...),\n",
|
||||
"):\n",
|
||||
" # 1. Валидация и очистка входных данных\n",
|
||||
" identifier = restore_data.identifier.strip().lower()\n",
|
||||
" identifier = restore_data.identifier.strip()\n",
|
||||
" \n",
|
||||
" if not identifier or not is_valid_email(identifier):\n",
|
||||
" raise HTTPException(status_code=400, detail=\"Некорректный формат email\")\n",
|
||||
@ -1821,7 +1821,10 @@
|
||||
" await redis_client.set(rate_limit_key, \"1\", ex=60)\n",
|
||||
"\n",
|
||||
" # 3. Поиск пользователя\n",
|
||||
" user = await users_collection.find_one({\"email\": identifier})\n",
|
||||
" user = await users_collection.find_one({'$or': [\n",
|
||||
" {'email': identifier}, \n",
|
||||
" {'email': identifier.lower()}\n",
|
||||
" ]})\n",
|
||||
" \n",
|
||||
" if not user:\n",
|
||||
" # Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно\n",
|
||||
|
||||
91
main.py
91
main.py
@ -782,12 +782,6 @@ async def login(
|
||||
identifier = safe_strip(login_data.identifier)
|
||||
await check_bruteforce(identifier)
|
||||
|
||||
users = [doc async for doc in users_collection.find({
|
||||
"$or": [
|
||||
{"email": identifier},
|
||||
{"email": identifier.lower()}
|
||||
]
|
||||
})]
|
||||
or_conditions = [
|
||||
{"email": identifier},
|
||||
{"email": identifier.lower()},
|
||||
@ -1334,43 +1328,80 @@ async def restore_api(
|
||||
request: Request,
|
||||
restore_data: RestoreRequest = Body(...),
|
||||
):
|
||||
email = restore_data.identifier.strip().lower()
|
||||
next = restore_data.next
|
||||
# 1. Валидация и очистка входных данных
|
||||
identifier = restore_data.identifier.strip()
|
||||
|
||||
if not identifier or not is_valid_email(identifier):
|
||||
raise HTTPException(status_code=400, detail="Некорректный формат email")
|
||||
|
||||
# 2. Проверка анти-спама / ограничения частоты запросов
|
||||
client_ip = request.client.host
|
||||
rate_limit_key = f"rate_limit:restore:{identifier}:{client_ip}"
|
||||
|
||||
# Проверяем, запрашивал ли пользователь сброс недавно (например, за последние 60 секунд)
|
||||
existing_request = await redis_client.get(rate_limit_key)
|
||||
|
||||
if existing_request:
|
||||
# Опционально: получаем TTL, чтобы сообщить пользователю, когда можно повторить попытку
|
||||
ttl = await redis_client.ttl(rate_limit_key)
|
||||
raise HTTPException(
|
||||
status_code=429,
|
||||
detail=f"Слишком много запросов. Попробуйте через {ttl} секунд."
|
||||
)
|
||||
|
||||
# Устанавливаем флаг ограничения частоты (истекает через 60 секунд)
|
||||
await redis_client.set(rate_limit_key, "1", ex=60)
|
||||
|
||||
# 3. Поиск пользователя
|
||||
user = await users_collection.find_one({'$or': [
|
||||
{'email': email},
|
||||
{'email': email.lower()}
|
||||
]})
|
||||
{'email': identifier},
|
||||
{'email': identifier.lower()}
|
||||
]})
|
||||
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail='Email не найден')
|
||||
# Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно
|
||||
# Но для удобства использования мы можем сказать "не найден".
|
||||
# Чтобы предотвратить перечисление, некоторые приложения всегда возвращают "OK", но отправляют письмо только если найдено.
|
||||
# Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления.
|
||||
raise HTTPException(status_code=404, detail="Пользователь с таким email не найден")
|
||||
|
||||
# 4. Генерация токена
|
||||
token = secrets.token_urlsafe(32)
|
||||
expires_at = datetime.utcnow() + timedelta(hours=1)
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
|
||||
|
||||
token_data = {
|
||||
"user_id": str(user["_id"]),
|
||||
"email": user["email"],
|
||||
"next": next,
|
||||
"next": restore_data.next,
|
||||
"expires_at": expires_at.isoformat()
|
||||
}
|
||||
|
||||
await redis_client.set(
|
||||
f"reset_token:{token}",
|
||||
json.dumps(token_data),
|
||||
ex=3600
|
||||
)
|
||||
|
||||
reset_link = f"{str(request.base_url).rstrip('/')}/restore/{token}"
|
||||
# 5. Сохранение токена сброса в Redis
|
||||
reset_token_key = f"reset_token:{token}"
|
||||
await redis_client.set(reset_token_key, json.dumps(token_data), ex=3600)
|
||||
|
||||
# 6. Генерация ссылки для сброса
|
||||
base_url = str(request.base_url).rstrip('/')
|
||||
reset_link = f"{base_url}/restore/{token}"
|
||||
|
||||
# 7. Асинхронная отправка email
|
||||
try:
|
||||
email_task.notify.delay(
|
||||
recipient=user['email'],
|
||||
context={
|
||||
'link': reset_link,
|
||||
'email': user['email']
|
||||
},
|
||||
subject='Восстановление пароля',
|
||||
template_name='restore.html'
|
||||
)
|
||||
except Exception as e:
|
||||
# Логирование ошибки
|
||||
print(f"Не удалось отправить письмо: {e}")
|
||||
# В зависимости от политики, вы можете захотеть удалить токен, если письмо не отправлено
|
||||
await redis_client.delete(reset_token_key)
|
||||
raise HTTPException(status_code=500, detail="Ошибка отправки письма")
|
||||
|
||||
email_task.notify.delay(
|
||||
user['email'],
|
||||
{'link': reset_link, 'email': user['email']},
|
||||
'Восстановление пароля',
|
||||
'restore.html'
|
||||
)
|
||||
|
||||
return {'ok': True}
|
||||
|
||||
|
||||
@ -1424,7 +1455,7 @@ async def restore_init(
|
||||
)
|
||||
|
||||
|
||||
app.include_router(router, tags=['Restore passwrod'])
|
||||
app.include_router(router, tags=['Восстановление пароля'])
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
async def login_(
|
||||
@ -2577,7 +2608,7 @@ async def post_competition(
|
||||
"serviceSocial": payload.serviceSocial,
|
||||
"userSocial": payload.userSocial,
|
||||
"short_desc": safe_strip(payload.short_desc) if payload.short_desc else None,
|
||||
"poster_file_id": poster_id,
|
||||
"poster_file_id": po/api/restorster_id,
|
||||
"poster_orig_id": poster_id,
|
||||
"rules_file_id": rules_id,
|
||||
"rules_link": payload.rules_link,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user