9a2aea038a
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
718 lines
23 KiB
Python
718 lines
23 KiB
Python
"""Authentication routes for cabinet."""
|
|
|
|
import asyncio
|
|
import hashlib
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.database.crud.user import create_user, create_user_by_email, get_user_by_id, get_user_by_telegram_id
|
|
from app.database.models import CabinetRefreshToken, User
|
|
|
|
from ..auth import (
|
|
create_access_token,
|
|
create_refresh_token,
|
|
get_token_payload,
|
|
hash_password,
|
|
validate_telegram_init_data,
|
|
validate_telegram_login_widget,
|
|
verify_password,
|
|
)
|
|
from ..auth.email_verification import (
|
|
generate_password_reset_token,
|
|
generate_verification_token,
|
|
get_password_reset_expires_at,
|
|
get_verification_expires_at,
|
|
is_token_expired,
|
|
)
|
|
from ..auth.jwt_handler import get_refresh_token_expires_at
|
|
from ..dependencies import get_cabinet_db, get_current_cabinet_user
|
|
from ..schemas.auth import (
|
|
AuthResponse,
|
|
EmailLoginRequest,
|
|
EmailRegisterRequest,
|
|
EmailRegisterStandaloneRequest,
|
|
EmailVerifyRequest,
|
|
PasswordForgotRequest,
|
|
PasswordResetRequest,
|
|
RefreshTokenRequest,
|
|
TelegramAuthRequest,
|
|
TelegramWidgetAuthRequest,
|
|
TokenResponse,
|
|
UserResponse,
|
|
)
|
|
from ..services.email_service import email_service
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix='/auth', tags=['Cabinet Auth'])
|
|
|
|
|
|
def _user_to_response(user: User) -> UserResponse:
|
|
"""Convert User model to UserResponse."""
|
|
return UserResponse(
|
|
id=user.id,
|
|
telegram_id=user.telegram_id,
|
|
username=user.username,
|
|
first_name=user.first_name,
|
|
last_name=user.last_name,
|
|
email=user.email,
|
|
email_verified=user.email_verified,
|
|
balance_kopeks=user.balance_kopeks,
|
|
balance_rubles=user.balance_rubles,
|
|
referral_code=user.referral_code,
|
|
language=user.language,
|
|
created_at=user.created_at,
|
|
auth_type=getattr(user, 'auth_type', 'telegram'), # Поддержка старых записей
|
|
)
|
|
|
|
|
|
def _create_auth_response(user: User) -> AuthResponse:
|
|
"""Create full auth response with tokens."""
|
|
access_token = create_access_token(user.id, user.telegram_id)
|
|
refresh_token = create_refresh_token(user.id)
|
|
expires_in = settings.get_cabinet_access_token_expire_minutes() * 60
|
|
|
|
return AuthResponse(
|
|
access_token=access_token,
|
|
refresh_token=refresh_token,
|
|
token_type='bearer',
|
|
expires_in=expires_in,
|
|
user=_user_to_response(user),
|
|
)
|
|
|
|
|
|
async def _store_refresh_token(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
refresh_token: str,
|
|
device_info: str | None = None,
|
|
) -> None:
|
|
"""Store refresh token hash in database."""
|
|
token_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
|
|
expires_at = get_refresh_token_expires_at()
|
|
|
|
# Check if token already exists (handles race conditions)
|
|
existing = await db.execute(select(CabinetRefreshToken).where(CabinetRefreshToken.token_hash == token_hash))
|
|
if existing.scalar_one_or_none():
|
|
# Token already stored, skip
|
|
return
|
|
|
|
token_record = CabinetRefreshToken(
|
|
user_id=user_id,
|
|
token_hash=token_hash,
|
|
device_info=device_info,
|
|
expires_at=expires_at,
|
|
)
|
|
db.add(token_record)
|
|
try:
|
|
await db.commit()
|
|
except Exception:
|
|
# Handle race condition if token was inserted between check and insert
|
|
await db.rollback()
|
|
|
|
|
|
@router.post('/telegram', response_model=AuthResponse)
|
|
async def auth_telegram(
|
|
request: TelegramAuthRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""
|
|
Authenticate using Telegram WebApp initData.
|
|
|
|
This endpoint validates the initData from Telegram WebApp and returns
|
|
JWT tokens for authenticated access.
|
|
"""
|
|
user_data = validate_telegram_init_data(request.init_data)
|
|
|
|
if not user_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid or expired Telegram authentication data',
|
|
)
|
|
|
|
telegram_id = user_data.get('id')
|
|
if not telegram_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Missing Telegram user ID',
|
|
)
|
|
|
|
user = await get_user_by_telegram_id(db, telegram_id)
|
|
|
|
# Get user data from initData
|
|
tg_username = user_data.get('username')
|
|
tg_first_name = user_data.get('first_name')
|
|
tg_last_name = user_data.get('last_name')
|
|
tg_language = user_data.get('language_code', 'ru')
|
|
|
|
if not user:
|
|
# Create new user from Telegram initData
|
|
logger.info(f'Creating new user from cabinet (initData): telegram_id={telegram_id}')
|
|
user = await create_user(
|
|
db=db,
|
|
telegram_id=telegram_id,
|
|
username=tg_username,
|
|
first_name=tg_first_name,
|
|
last_name=tg_last_name,
|
|
language=tg_language,
|
|
)
|
|
logger.info(f'User created successfully: id={user.id}, telegram_id={user.telegram_id}')
|
|
else:
|
|
# Update user info from initData (like bot middleware does)
|
|
updated = False
|
|
if tg_username and tg_username != user.username:
|
|
user.username = tg_username
|
|
updated = True
|
|
if tg_first_name and tg_first_name != user.first_name:
|
|
user.first_name = tg_first_name
|
|
updated = True
|
|
if tg_last_name and tg_last_name != user.last_name:
|
|
user.last_name = tg_last_name
|
|
updated = True
|
|
if updated:
|
|
logger.info(f'User {user.id} profile updated from initData')
|
|
|
|
if user.status != 'active':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail='User account is not active',
|
|
)
|
|
|
|
# Update last login
|
|
user.cabinet_last_login = datetime.utcnow()
|
|
await db.commit()
|
|
|
|
response = _create_auth_response(user)
|
|
|
|
# Store refresh token
|
|
await _store_refresh_token(db, user.id, response.refresh_token)
|
|
|
|
return response
|
|
|
|
|
|
@router.post('/telegram/widget', response_model=AuthResponse)
|
|
async def auth_telegram_widget(
|
|
request: TelegramWidgetAuthRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""
|
|
Authenticate using Telegram Login Widget data.
|
|
|
|
This endpoint validates data from Telegram Login Widget and returns
|
|
JWT tokens for authenticated access.
|
|
"""
|
|
widget_data = request.model_dump()
|
|
|
|
if not validate_telegram_login_widget(widget_data):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid or expired Telegram authentication data',
|
|
)
|
|
|
|
user = await get_user_by_telegram_id(db, request.id)
|
|
|
|
if not user:
|
|
# Create new user from Telegram data
|
|
logger.info(f'Creating new user from cabinet: telegram_id={request.id}, username={request.username}')
|
|
user = await create_user(
|
|
db=db,
|
|
telegram_id=request.id,
|
|
username=request.username,
|
|
first_name=request.first_name,
|
|
last_name=request.last_name,
|
|
language='ru',
|
|
)
|
|
logger.info(f'User created successfully: id={user.id}, telegram_id={user.telegram_id}')
|
|
|
|
if user.status != 'active':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail='User account is not active',
|
|
)
|
|
|
|
# Update user info from widget data
|
|
if request.username and request.username != user.username:
|
|
user.username = request.username
|
|
if request.first_name and request.first_name != user.first_name:
|
|
user.first_name = request.first_name
|
|
if request.last_name != user.last_name:
|
|
user.last_name = request.last_name
|
|
|
|
user.cabinet_last_login = datetime.utcnow()
|
|
await db.commit()
|
|
|
|
response = _create_auth_response(user)
|
|
await _store_refresh_token(db, user.id, response.refresh_token)
|
|
|
|
return response
|
|
|
|
|
|
@router.post('/email/register')
|
|
async def register_email(
|
|
request: EmailRegisterRequest,
|
|
user: User = Depends(get_current_cabinet_user),
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""
|
|
Register/link email to existing Telegram account.
|
|
|
|
Requires valid JWT token from Telegram authentication.
|
|
Sends verification email to the provided address.
|
|
"""
|
|
# Check if email already exists
|
|
existing_user = await db.execute(select(User).where(User.email == request.email))
|
|
if existing_user.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='This email is already registered',
|
|
)
|
|
|
|
# Check if user already has email
|
|
if user.email and user.email_verified:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='You already have a verified email',
|
|
)
|
|
|
|
# Generate verification token
|
|
verification_token = generate_verification_token()
|
|
verification_expires = get_verification_expires_at()
|
|
|
|
# Update user
|
|
user.email = request.email
|
|
user.email_verified = False
|
|
user.password_hash = hash_password(request.password)
|
|
user.email_verification_token = verification_token
|
|
user.email_verification_expires = verification_expires
|
|
|
|
await db.commit()
|
|
|
|
# Send verification email asynchronously (smtplib is blocking)
|
|
if settings.is_cabinet_email_verification_enabled() and email_service.is_configured():
|
|
# TODO: Get actual verification URL from settings
|
|
verification_url = 'https://example.com/cabinet/verify-email'
|
|
await asyncio.to_thread(
|
|
email_service.send_verification_email,
|
|
to_email=request.email,
|
|
verification_token=verification_token,
|
|
verification_url=verification_url,
|
|
username=user.first_name,
|
|
)
|
|
|
|
return {
|
|
'message': 'Verification email sent',
|
|
'email': request.email,
|
|
}
|
|
|
|
|
|
@router.post('/email/register/standalone', response_model=AuthResponse)
|
|
async def register_email_standalone(
|
|
request: EmailRegisterStandaloneRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""
|
|
Register new account with email and password.
|
|
|
|
This endpoint creates a new user WITHOUT requiring Telegram authentication.
|
|
An email verification link will be sent to confirm the email address.
|
|
|
|
The user can login immediately but some features may be restricted
|
|
until email is verified.
|
|
|
|
If TEST_EMAIL is configured, test email accounts are auto-verified.
|
|
"""
|
|
# Check if this is a test email registration
|
|
is_test_email = settings.is_test_email(request.email)
|
|
|
|
if is_test_email:
|
|
# Validate test email password
|
|
if not settings.validate_test_email_password(request.email, request.password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Invalid test email password',
|
|
)
|
|
logger.info(f'Test email registration: {request.email}')
|
|
|
|
# Проверить что email не занят
|
|
existing = await db.execute(select(User).where(User.email == request.email))
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='This email is already registered',
|
|
)
|
|
|
|
# Хешировать пароль
|
|
password_hash = hash_password(request.password)
|
|
|
|
# Создать пользователя
|
|
user = await create_user_by_email(
|
|
db=db,
|
|
email=request.email,
|
|
password_hash=password_hash,
|
|
first_name=request.first_name,
|
|
language=request.language,
|
|
)
|
|
|
|
# Для тестового email - автоматически верифицировать
|
|
if is_test_email:
|
|
user.email_verified = True
|
|
user.email_verified_at = datetime.utcnow()
|
|
await db.commit()
|
|
logger.info(f'Test email auto-verified: {request.email}, user_id={user.id}')
|
|
else:
|
|
# Сгенерировать токен верификации
|
|
verification_token = generate_verification_token()
|
|
verification_expires = get_verification_expires_at()
|
|
|
|
user.email_verification_token = verification_token
|
|
user.email_verification_expires = verification_expires
|
|
await db.commit()
|
|
|
|
# Отправить email верификации
|
|
if settings.is_cabinet_email_verification_enabled() and email_service.is_configured():
|
|
cabinet_url = getattr(settings, 'CABINET_URL', 'https://example.com/cabinet')
|
|
verification_url = f'{cabinet_url}/verify-email?token={verification_token}'
|
|
await asyncio.to_thread(
|
|
email_service.send_verification_email,
|
|
to_email=request.email,
|
|
verification_token=verification_token,
|
|
verification_url=verification_url,
|
|
username=user.first_name or 'User',
|
|
)
|
|
|
|
# Создать токены и вернуть ответ
|
|
response = _create_auth_response(user)
|
|
await _store_refresh_token(db, user.id, response.refresh_token)
|
|
|
|
return response
|
|
|
|
|
|
@router.post('/email/verify')
|
|
async def verify_email(
|
|
request: EmailVerifyRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Verify email with token."""
|
|
# Find user with this token
|
|
result = await db.execute(select(User).where(User.email_verification_token == request.token))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Invalid verification token',
|
|
)
|
|
|
|
if is_token_expired(user.email_verification_expires):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Verification token has expired',
|
|
)
|
|
|
|
# Mark email as verified
|
|
user.email_verified = True
|
|
user.email_verified_at = datetime.utcnow()
|
|
user.email_verification_token = None
|
|
user.email_verification_expires = None
|
|
|
|
await db.commit()
|
|
|
|
return {'message': 'Email verified successfully'}
|
|
|
|
|
|
@router.post('/email/resend')
|
|
async def resend_verification(
|
|
user: User = Depends(get_current_cabinet_user),
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Resend verification email."""
|
|
if not user.email:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='No email address to verify',
|
|
)
|
|
|
|
if user.email_verified:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Email is already verified',
|
|
)
|
|
|
|
# Generate new token
|
|
verification_token = generate_verification_token()
|
|
verification_expires = get_verification_expires_at()
|
|
|
|
user.email_verification_token = verification_token
|
|
user.email_verification_expires = verification_expires
|
|
|
|
await db.commit()
|
|
|
|
# Send verification email asynchronously (smtplib is blocking)
|
|
if settings.is_cabinet_email_verification_enabled() and email_service.is_configured():
|
|
verification_url = 'https://example.com/cabinet/verify-email'
|
|
await asyncio.to_thread(
|
|
email_service.send_verification_email,
|
|
to_email=user.email,
|
|
verification_token=verification_token,
|
|
verification_url=verification_url,
|
|
username=user.first_name,
|
|
)
|
|
elif not settings.is_cabinet_email_verification_enabled():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Email verification is disabled',
|
|
)
|
|
elif not email_service.is_configured():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail='Email service is not configured',
|
|
)
|
|
|
|
return {'message': 'Verification email sent'}
|
|
|
|
|
|
@router.post('/email/login', response_model=AuthResponse)
|
|
async def login_email(
|
|
request: EmailLoginRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Login with email and password.
|
|
|
|
Test email accounts (configured via TEST_EMAIL) bypass email verification.
|
|
"""
|
|
# Check if this is a test email login
|
|
is_test_email = settings.is_test_email(request.email)
|
|
|
|
# Find user by email
|
|
result = await db.execute(select(User).where(User.email == request.email))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
# For test email - auto-create user if not exists
|
|
if is_test_email and settings.validate_test_email_password(request.email, request.password):
|
|
logger.info(f'Test email login - creating new user: {request.email}')
|
|
password_hash = hash_password(request.password)
|
|
user = await create_user_by_email(
|
|
db=db,
|
|
email=request.email,
|
|
password_hash=password_hash,
|
|
first_name='Test User',
|
|
language='ru',
|
|
)
|
|
user.email_verified = True
|
|
user.email_verified_at = datetime.utcnow()
|
|
await db.commit()
|
|
else:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid email or password',
|
|
)
|
|
|
|
if not user.password_hash:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Password login not configured for this account',
|
|
)
|
|
|
|
if not verify_password(request.password, user.password_hash):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid email or password',
|
|
)
|
|
|
|
# Test email bypasses verification check
|
|
if not user.email_verified and not is_test_email:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail='Please verify your email first',
|
|
)
|
|
|
|
if user.status != 'active':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail='User account is not active',
|
|
)
|
|
|
|
user.cabinet_last_login = datetime.utcnow()
|
|
await db.commit()
|
|
|
|
response = _create_auth_response(user)
|
|
await _store_refresh_token(db, user.id, response.refresh_token)
|
|
|
|
return response
|
|
|
|
|
|
@router.post('/refresh', response_model=TokenResponse)
|
|
async def refresh_token(
|
|
request: RefreshTokenRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Refresh access token using refresh token."""
|
|
payload = get_token_payload(request.refresh_token, expected_type='refresh')
|
|
|
|
if not payload:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid or expired refresh token',
|
|
)
|
|
|
|
try:
|
|
user_id = int(payload.get('sub'))
|
|
except (TypeError, ValueError):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Invalid token payload',
|
|
)
|
|
|
|
# Verify token exists in database and is not revoked
|
|
token_hash = hashlib.sha256(request.refresh_token.encode()).hexdigest()
|
|
result = await db.execute(
|
|
select(CabinetRefreshToken).where(
|
|
CabinetRefreshToken.token_hash == token_hash,
|
|
CabinetRefreshToken.revoked_at.is_(None),
|
|
)
|
|
)
|
|
token_record = result.scalar_one_or_none()
|
|
|
|
if not token_record:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Refresh token not found or revoked',
|
|
)
|
|
|
|
if not token_record.is_valid:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='Refresh token is no longer valid',
|
|
)
|
|
|
|
user = await get_user_by_id(db, user_id)
|
|
|
|
if not user or user.status != 'active':
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail='User not found or inactive',
|
|
)
|
|
|
|
access_token = create_access_token(user.id, user.telegram_id)
|
|
expires_in = settings.get_cabinet_access_token_expire_minutes() * 60
|
|
|
|
return TokenResponse(
|
|
access_token=access_token,
|
|
refresh_token=request.refresh_token,
|
|
token_type='bearer',
|
|
expires_in=expires_in,
|
|
)
|
|
|
|
|
|
@router.post('/logout')
|
|
async def logout(
|
|
request: RefreshTokenRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Logout and revoke refresh token."""
|
|
token_hash = hashlib.sha256(request.refresh_token.encode()).hexdigest()
|
|
|
|
result = await db.execute(
|
|
select(CabinetRefreshToken).where(
|
|
CabinetRefreshToken.token_hash == token_hash,
|
|
)
|
|
)
|
|
token_record = result.scalar_one_or_none()
|
|
|
|
if token_record:
|
|
token_record.revoked_at = datetime.utcnow()
|
|
await db.commit()
|
|
|
|
return {'message': 'Logged out successfully'}
|
|
|
|
|
|
@router.post('/password/forgot')
|
|
async def forgot_password(
|
|
request: PasswordForgotRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Request password reset."""
|
|
result = await db.execute(select(User).where(User.email == request.email))
|
|
user = result.scalar_one_or_none()
|
|
|
|
# Always return success to prevent email enumeration
|
|
if not user or not user.email_verified:
|
|
return {'message': 'If the email exists, a password reset link has been sent'}
|
|
|
|
# Generate reset token
|
|
reset_token = generate_password_reset_token()
|
|
reset_expires = get_password_reset_expires_at()
|
|
|
|
user.password_reset_token = reset_token
|
|
user.password_reset_expires = reset_expires
|
|
|
|
await db.commit()
|
|
|
|
# Send reset email asynchronously (smtplib is blocking)
|
|
if email_service.is_configured():
|
|
reset_url = 'https://example.com/cabinet/reset-password'
|
|
await asyncio.to_thread(
|
|
email_service.send_password_reset_email,
|
|
to_email=user.email,
|
|
reset_token=reset_token,
|
|
reset_url=reset_url,
|
|
username=user.first_name,
|
|
)
|
|
|
|
return {'message': 'If the email exists, a password reset link has been sent'}
|
|
|
|
|
|
@router.post('/password/reset')
|
|
async def reset_password(
|
|
request: PasswordResetRequest,
|
|
db: AsyncSession = Depends(get_cabinet_db),
|
|
):
|
|
"""Reset password with token."""
|
|
result = await db.execute(select(User).where(User.password_reset_token == request.token))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Invalid reset token',
|
|
)
|
|
|
|
if is_token_expired(user.password_reset_expires):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail='Reset token has expired',
|
|
)
|
|
|
|
# Update password
|
|
user.password_hash = hash_password(request.password)
|
|
user.password_reset_token = None
|
|
user.password_reset_expires = None
|
|
|
|
await db.commit()
|
|
|
|
return {'message': 'Password reset successfully'}
|
|
|
|
|
|
@router.get('/me', response_model=UserResponse)
|
|
async def get_current_user(
|
|
user: User = Depends(get_current_cabinet_user),
|
|
):
|
|
"""Get current authenticated user info."""
|
|
return _user_to_response(user)
|
|
|
|
|
|
@router.get('/me/is-admin')
|
|
async def check_is_admin(
|
|
user: User = Depends(get_current_cabinet_user),
|
|
):
|
|
"""Check if current user is an admin."""
|
|
is_admin = settings.is_admin(telegram_id=user.telegram_id, email=user.email if user.email_verified else None)
|
|
return {'is_admin': is_admin}
|