init commit
This commit is contained in:
218
.history/app/services/auth_service_20251210210906.py
Normal file
218
.history/app/services/auth_service_20251210210906.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Authentication Service - User login, Telegram binding, Token management
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any, Tuple
|
||||
import secrets
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.models import User, Session as DBSession, TelegramIdentity
|
||||
from app.security.jwt_manager import jwt_manager
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthService:
|
||||
"""
|
||||
Handles user authentication, token management, and Telegram binding.
|
||||
"""
|
||||
|
||||
# Configuration
|
||||
TELEGRAM_BINDING_CODE_TTL = 600 # 10 minutes
|
||||
BINDING_CODE_LENGTH = 24
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
async def create_telegram_binding_code(self, chat_id: int) -> str:
|
||||
"""
|
||||
Generate temporary code for Telegram user binding.
|
||||
|
||||
Flow:
|
||||
1. User sends /start to bot
|
||||
2. Bot generates binding code
|
||||
3. Bot sends user a link: https://app.com/auth/telegram?code=ABC123
|
||||
4. User clicks link (authenticated or creates account)
|
||||
5. Code is confirmed, JWT issued
|
||||
|
||||
Returns:
|
||||
Binding code (24-char random)
|
||||
"""
|
||||
|
||||
# Generate code
|
||||
code = secrets.token_urlsafe(TELEGRAM_BINDING_CODE_LENGTH)
|
||||
|
||||
# Store in Redis (would be: redis.setex(f"telegram:code:{code}", TTL, chat_id))
|
||||
# For MVP: Store in memory or DB with expiry
|
||||
|
||||
logger.info(f"Generated Telegram binding code for chat_id={chat_id}")
|
||||
|
||||
return code
|
||||
|
||||
async def confirm_telegram_binding(
|
||||
self,
|
||||
user_id: int,
|
||||
chat_id: int,
|
||||
code: str,
|
||||
username: Optional[str] = None,
|
||||
first_name: Optional[str] = None,
|
||||
last_name: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Confirm Telegram binding and create identity.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 123,
|
||||
"chat_id": 12345,
|
||||
"jwt_token": "eyJhbGc...",
|
||||
"expires_at": "2024-01-09T12:30:00Z"
|
||||
}
|
||||
"""
|
||||
|
||||
# Verify code (would check Redis)
|
||||
# For MVP: Assume code is valid
|
||||
|
||||
# Create or update Telegram identity
|
||||
identity = self.db.query(TelegramIdentity).filter(
|
||||
TelegramIdentity.user_id == user_id
|
||||
).first()
|
||||
|
||||
if identity:
|
||||
# Update existing
|
||||
identity.chat_id = chat_id
|
||||
identity.username = username
|
||||
identity.first_name = first_name
|
||||
identity.last_name = last_name
|
||||
identity.verified_at = datetime.utcnow()
|
||||
identity.updated_at = datetime.utcnow()
|
||||
else:
|
||||
# Create new
|
||||
identity = TelegramIdentity(
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
verified_at=datetime.utcnow(),
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
self.db.add(identity)
|
||||
|
||||
# Generate JWT token for bot
|
||||
jwt_token = jwt_manager.create_access_token(
|
||||
user_id=user_id,
|
||||
family_ids=[], # Will be loaded from user's families
|
||||
)
|
||||
|
||||
self.db.commit()
|
||||
|
||||
logger.info(f"Telegram binding confirmed: user_id={user_id}, chat_id={chat_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": user_id,
|
||||
"chat_id": chat_id,
|
||||
"jwt_token": jwt_token,
|
||||
"expires_at": (datetime.utcnow() + timedelta(minutes=15)).isoformat() + "Z",
|
||||
}
|
||||
|
||||
async def authenticate_telegram_user(
|
||||
self,
|
||||
chat_id: int,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Authenticate user by Telegram chat_id.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"user_id": 123,
|
||||
"chat_id": 12345,
|
||||
"jwt_token": "...",
|
||||
}
|
||||
Or None if not found
|
||||
"""
|
||||
|
||||
# Find Telegram identity
|
||||
identity = self.db.query(TelegramIdentity).filter(
|
||||
TelegramIdentity.chat_id == chat_id,
|
||||
TelegramIdentity.verified_at.isnot(None),
|
||||
).first()
|
||||
|
||||
if not identity:
|
||||
return None
|
||||
|
||||
# Generate JWT
|
||||
jwt_token = jwt_manager.create_access_token(
|
||||
user_id=identity.user_id,
|
||||
)
|
||||
|
||||
return {
|
||||
"user_id": identity.user_id,
|
||||
"chat_id": chat_id,
|
||||
"jwt_token": jwt_token,
|
||||
}
|
||||
|
||||
async def create_session(
|
||||
self,
|
||||
user_id: int,
|
||||
device_id: Optional[str] = None,
|
||||
ip_address: Optional[str] = None,
|
||||
user_agent: Optional[str] = None,
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Create new session with refresh token.
|
||||
|
||||
Returns:
|
||||
(access_token, refresh_token)
|
||||
"""
|
||||
|
||||
# Create access token
|
||||
access_token = jwt_manager.create_access_token(user_id=user_id, device_id=device_id)
|
||||
|
||||
# Create refresh token
|
||||
refresh_token = jwt_manager.create_refresh_token(user_id=user_id, device_id=device_id)
|
||||
|
||||
# Store session in DB
|
||||
session = DBSession(
|
||||
user_id=user_id,
|
||||
refresh_token_hash=self._hash_token(refresh_token),
|
||||
device_id=device_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
expires_at=datetime.utcnow() + timedelta(days=30),
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
self.db.add(session)
|
||||
self.db.commit()
|
||||
|
||||
return access_token, refresh_token
|
||||
|
||||
async def refresh_access_token(
|
||||
self,
|
||||
refresh_token: str,
|
||||
user_id: int,
|
||||
) -> str:
|
||||
"""Issue new access token using refresh token"""
|
||||
|
||||
# Verify refresh token
|
||||
try:
|
||||
token_payload = jwt_manager.verify_token(refresh_token)
|
||||
if token_payload.type != "refresh":
|
||||
raise ValueError("Not a refresh token")
|
||||
except ValueError:
|
||||
raise ValueError("Invalid refresh token")
|
||||
|
||||
# Create new access token
|
||||
new_access_token = jwt_manager.create_access_token(user_id=user_id)
|
||||
|
||||
return new_access_token
|
||||
|
||||
@staticmethod
|
||||
def _hash_token(token: str) -> str:
|
||||
"""Hash token for storage"""
|
||||
import hashlib
|
||||
return hashlib.sha256(token.encode()).hexdigest()
|
||||
Reference in New Issue
Block a user