Вход и регистрация по логину и паролю
Игроки входили только через Telegram, а вход по нику без пароля (stub) есть лишь в dev. Теперь основной вход во всех окружениях: POST /api/auth/register и POST /api/auth/login, логин — это ник. Stub в прод не переносится: он пускает без секрета и по-прежнему живёт только в dev. Новый код лежит в прод-модуле auth/password.py и dev-модули не импортирует. Схема БД не меняется: колонка password_hash и провайдер local есть с первой миграции, на них построен вход админа. Пароль от 8 символов и не длиннее 72 байт: дальше bcrypt 5 бросает ValueError. Схема API режет тело длиннее 128 символов ещё до bcrypt. Игроком входит только role='player', так что учётка админа не открывает сессию игрока, и наоборот. Неизвестный логин и аккаунт без пароля сверяются с фиктивным хешем и получают ту же 401 INVALID_CREDENTIALS: по ответу и его времени нельзя понять, есть ли логин. От перебора — скользящее окно 15 минут в памяти процесса (рассчитано на один воркер, как SSE-шина): 5 неудач на пару «IP + логин» и 20 на IP, дальше 429 TOO_MANY_ATTEMPTS с retry_after. Пока блок стоит, пароль не проверяется вовсе. Успешный вход сбрасывает счётчик пары, но не IP. В MeRead появилось has_password: по нему фронт попросит задать пароль тех, у кого его нет. Метод password добавлен в /auth/config. #24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
This commit is contained in:
@@ -7,9 +7,11 @@ from pathlib import Path
|
||||
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.auth.password import validate_new_password
|
||||
from app.auth.provider import ExternalIdentity
|
||||
from app.core.config import settings
|
||||
from app.core.errors import NicknameTakenError, NotFoundError, ValidationError
|
||||
from app.core.security import hash_password
|
||||
from app.core.timeutil import utcnow
|
||||
from app.models import AuthIdentity, Faction, User
|
||||
|
||||
@@ -132,6 +134,26 @@ def register_from_identity(
|
||||
return _create_from_identity(session, identity, nickname)
|
||||
|
||||
|
||||
def register_local(session: Session, nickname: str, password: str) -> User:
|
||||
"""Регистрация по логину и паролю. Логин — это ник; Telegram можно привязать позже."""
|
||||
nickname = (nickname or "").strip()
|
||||
if not nickname_format_ok(nickname):
|
||||
raise ValidationError("Ник: 2–64 символа, буквы/цифры/пробел/.-_")
|
||||
if not nickname_available(session, nickname):
|
||||
raise NicknameTakenError()
|
||||
validate_new_password(password)
|
||||
user = User(
|
||||
nickname=nickname,
|
||||
role="player",
|
||||
auth_provider="local",
|
||||
password_hash=hash_password(password),
|
||||
)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
def update_nickname(session: Session, user: User, new_nickname: str) -> User:
|
||||
new_nickname = (new_nickname or "").strip()
|
||||
if not nickname_format_ok(new_nickname):
|
||||
|
||||
Reference in New Issue
Block a user