Implement email-based 2FA authentication for Telegram bot with Oracle integration fixes

This commit adds a complete email authentication flow for the Telegram bot, allowing users to login with email + password instead of web app linking codes. Includes critical bug fixes for Oracle integration.

**New Features:**
- Email-based 2FA authentication with 6-digit codes sent via SMTP
- Backend endpoints: verify-email and login-with-email
- ConversationHandler for email authentication flow in Telegram bot
- Session token verification to prevent user ID spoofing
- Rate limiting (5 attempts per 5 minutes)
- Email code expiry (5 minutes) with automatic cleanup

**Bug Fixes:**
- Fixed Oracle column name: ACTIV → INACTIV (with inverted logic)
- Fixed Oracle password verification: verificautilizator returns checksum, not user_id
- Fixed username case sensitivity: Oracle usernames must be uppercase
- Fixed SMTP connection: use start_tls parameter instead of manual STARTTLS
- Added middleware exclusions for public email auth endpoints

**Backend Changes:**
- Added verify-email endpoint (public) in telegram.py
- Added login-with-email endpoint (public) with rate limiting and session verification
- Updated middleware exclusions in main.py and auth_middleware_wrapper.py
- Added AUTH_SESSION_SECRET configuration for session token signing

**Telegram Bot Changes:**
- New modules: app/auth/email_auth.py, app/bot/email_handlers.py
- New utilities: app/utils/email_service.py (SMTP email sending)
- Updated handlers.py: ignore callbacks handled by ConversationHandler
- Updated menus.py: show Login button for unauthenticated users
- Updated API client: verify_email() and login_with_email() methods
- Database: email_auth_codes table with cleanup task

**Configuration:**
- Added SMTP configuration to telegram-bot .env.example
- Added AUTH_SESSION_SECRET to backend .env.example
- Updated .gitignore: exclude temporary files (*.pid, *.checksum, test scripts)

**Dependencies:**
- Added aiosmtplib for async SMTP email sending

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 12:00:46 +02:00
parent 1378ee1e6a
commit 706062dc0f
19 changed files with 2032 additions and 101 deletions

View File

@@ -30,7 +30,12 @@ from telegram.ext import (
)
# Import database initialization
from app.db import init_database, cleanup_expired_codes, cleanup_expired_sessions
from app.db import (
init_database,
cleanup_expired_codes,
cleanup_expired_sessions,
cleanup_expired_email_codes
)
# Import bot handlers
from app.bot.handlers import (
@@ -61,6 +66,9 @@ from app.bot.handlers import (
error_handler
)
# Import email authentication handler
from app.bot.email_handlers import email_login_handler
# Import internal API
from app.internal_api import internal_api
@@ -93,6 +101,9 @@ def create_telegram_application() -> Application:
# Create application
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
# Register email authentication conversation handler (must be before other handlers)
application.add_handler(email_login_handler)
# Register essential command handlers
application.add_handler(CommandHandler("start", start_command))
application.add_handler(CommandHandler("menu", menu_command))
@@ -186,7 +197,8 @@ async def startup():
logger.info("Cleaning up expired data...")
expired_codes = await cleanup_expired_codes()
expired_sessions = await cleanup_expired_sessions()
logger.info(f"✅ Cleanup complete: {expired_codes} codes, {expired_sessions} sessions removed")
expired_email_codes = await cleanup_expired_email_codes()
logger.info(f"✅ Cleanup complete: {expired_codes} codes, {expired_sessions} sessions, {expired_email_codes} email codes removed")
except Exception as e:
logger.warning(f"⚠️ Cleanup failed (non-critical): {e}")
@@ -204,7 +216,7 @@ async def shutdown():
async def scheduled_cleanup():
"""
Background task to periodically clean up expired data.
Runs every hour to remove expired auth codes and sessions.
Runs every hour to remove expired auth codes, sessions, and email codes.
"""
while True:
try:
@@ -212,7 +224,8 @@ async def scheduled_cleanup():
logger.info("🧹 Running scheduled cleanup...")
expired_codes = await cleanup_expired_codes()
expired_sessions = await cleanup_expired_sessions()
logger.info(f"✅ Scheduled cleanup: {expired_codes} codes, {expired_sessions} sessions removed")
expired_email_codes = await cleanup_expired_email_codes()
logger.info(f"✅ Scheduled cleanup: {expired_codes} codes, {expired_sessions} sessions, {expired_email_codes} email codes removed")
except Exception as e:
logger.error(f"❌ Error in scheduled cleanup: {e}")