feat: Complete API authentication system with email & Telegram support

- Add email/password registration endpoint (/api/v1/auth/register)
- Add JWT token endpoints for Telegram users (/api/v1/auth/token/get, /api/v1/auth/token/refresh-telegram)
- Enhance User model to support both email and Telegram authentication
- Fix JWT token handling: convert sub to string (RFC compliance with PyJWT 2.10.1+)
- Fix bot API calls: filter None values from query parameters
- Fix JWT extraction from Redis: handle both bytes and string returns
- Add public endpoints to JWT middleware: /api/v1/auth/register, /api/v1/auth/token/*
- Update bot commands: /register (one-tap), /link (account linking), /start (options)
- Create complete database schema migration with email auth support
- Remove deprecated version attribute from docker-compose.yml
- Add service dependency: bot waits for web service startup

Features:
- Dual authentication: email/password OR Telegram ID
- JWT tokens with 15-min access + 30-day refresh lifetime
- Redis-based token storage with TTL
- Comprehensive API documentation and integration guides
- Test scripts and Python examples
- Full deployment checklist

Database changes:
- User model: added email, password_hash, email_verified (nullable fields)
- telegram_id now nullable to support email-only users
- Complete schema with families, accounts, categories, transactions, budgets, goals

Status: Production-ready with all tests passing
This commit is contained in:
2025-12-11 21:00:34 +09:00
parent b642d1e9e9
commit 23a9d975a9
21 changed files with 4832 additions and 480 deletions

View File

@@ -7,12 +7,20 @@ from app.db.database import Base
class User(Base):
"""User model - represents a Telegram user"""
"""User model - represents a user with email/password or Telegram binding"""
__tablename__ = "users"
id = Column(Integer, primary_key=True)
telegram_id = Column(Integer, unique=True, nullable=False, index=True)
# Authentication - Email/Password
email = Column(String(255), unique=True, nullable=True, index=True)
password_hash = Column(String(255), nullable=True)
# Authentication - Telegram
telegram_id = Column(Integer, unique=True, nullable=True, index=True)
# User info
username = Column(String(255), nullable=True)
first_name = Column(String(255), nullable=True)
last_name = Column(String(255), nullable=True)
@@ -20,6 +28,7 @@ class User(Base):
# Account status
is_active = Column(Boolean, default=True)
email_verified = Column(Boolean, default=False)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
@@ -32,4 +41,5 @@ class User(Base):
transactions = relationship("Transaction", back_populates="user")
def __repr__(self) -> str:
return f"<User(id={self.id}, telegram_id={self.telegram_id}, username={self.username})>"
auth_method = "email" if self.email else "telegram" if self.telegram_id else "none"
return f"<User(id={self.id}, email={self.email}, telegram_id={self.telegram_id}, auth={auth_method})>"