Пароль и привязка Telegram в профиле, пароль игрока из админки
PUT /api/users/me/password задаёт или меняет пароль. Первый раз текущий пароль
не нужен: так его задают аккаунты из Telegram и все, кто появился до паролей.
Если пароль уже есть, нужен текущий. Иначе оставленная открытой сессия позволила
бы отобрать аккаунт насовсем, поэтому подбор текущего тоже ограничен: 5 неудач
на аккаунт за 15 минут. Ошибка 403 WRONG_CURRENT_PASSWORD, а не 401, чтобы фронт
не принял её за истёкшую сессию.
POST /api/users/me/telegram привязывает Telegram к аккаунту, созданному по
паролю. Подпись виджета проверяется так же, как при входе, ник не меняется.
Связка пишется в auth_identity, как при регистрации через Telegram, поэтому
следующий вход через Telegram попадает в этот аккаунт. Telegram, привязанный к
другому аккаунту, даёт 409 TELEGRAM_TAKEN, повторная привязка — 409
TELEGRAM_ALREADY_LINKED.
PUT /api/admin/users/{id}/password — способ восстановить забытый пароль: почту
приложение не хранит. Работает только для игроков, пароль админа по-прежнему
задаётся в .env. В аудит пишется только факт смены, без пароля. Сборка
AdminUserRead вынесена в хелпер, в ответе появилось has_password.
#24
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
This commit is contained in:
@@ -68,24 +68,26 @@ def admin_me(admin: User = Depends(get_current_admin)) -> s.AdminMe:
|
||||
|
||||
# ─── Пользователи ────────────────────────────────────────────────────────────
|
||||
|
||||
def _admin_user_read(u: User) -> s.AdminUserRead:
|
||||
return s.AdminUserRead(
|
||||
id=u.id, # type: ignore[arg-type]
|
||||
nickname=u.nickname,
|
||||
role=u.role,
|
||||
is_active=u.is_active,
|
||||
auth_provider=u.auth_provider,
|
||||
telegram_id=u.telegram_id,
|
||||
created_at=iso_utc(u.created_at),
|
||||
has_password=u.password_hash is not None,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[s.AdminUserRead])
|
||||
def list_users(
|
||||
query: str | None = Query(None),
|
||||
session: Session = Depends(get_session),
|
||||
_admin: User = Depends(get_current_admin),
|
||||
) -> list[s.AdminUserRead]:
|
||||
return [
|
||||
s.AdminUserRead(
|
||||
id=u.id, # type: ignore[arg-type]
|
||||
nickname=u.nickname,
|
||||
role=u.role,
|
||||
is_active=u.is_active,
|
||||
auth_provider=u.auth_provider,
|
||||
telegram_id=u.telegram_id,
|
||||
created_at=iso_utc(u.created_at),
|
||||
)
|
||||
for u in admin_service.list_users(session, query)
|
||||
]
|
||||
return [_admin_user_read(u) for u in admin_service.list_users(session, query)]
|
||||
|
||||
|
||||
@router.patch("/users/{user_id}", response_model=s.AdminUserRead)
|
||||
@@ -107,15 +109,30 @@ def update_user(
|
||||
ip=client_ip(request),
|
||||
)
|
||||
session.commit()
|
||||
return s.AdminUserRead(
|
||||
id=u.id, # type: ignore[arg-type]
|
||||
nickname=u.nickname,
|
||||
role=u.role,
|
||||
is_active=u.is_active,
|
||||
auth_provider=u.auth_provider,
|
||||
telegram_id=u.telegram_id,
|
||||
created_at=iso_utc(u.created_at),
|
||||
return _admin_user_read(u)
|
||||
|
||||
|
||||
@router.put("/users/{user_id}/password", response_model=s.AdminUserRead)
|
||||
def set_user_password(
|
||||
user_id: int,
|
||||
body: s.AdminPasswordSet,
|
||||
request: Request,
|
||||
session: Session = Depends(get_session),
|
||||
admin: User = Depends(get_current_admin),
|
||||
) -> s.AdminUserRead:
|
||||
"""Задать игроку новый пароль — когда он забыл свой. Сам пароль в аудит не пишется."""
|
||||
u = admin_service.set_player_password(session, user_id, body.new_password)
|
||||
audit_service.record(
|
||||
session,
|
||||
actor_id=admin.id,
|
||||
action="update",
|
||||
entity_type="user",
|
||||
entity_id=user_id,
|
||||
payload={"password": "set_by_admin"},
|
||||
ip=client_ip(request),
|
||||
)
|
||||
session.commit()
|
||||
return _admin_user_read(u)
|
||||
|
||||
|
||||
# Удаление аккаунта — намеренно НЕ здесь: это dev-only возможность, вынесена в
|
||||
|
||||
Reference in New Issue
Block a user