Files
roa2web-service-auto/TELEGRAM_EMAIL_AUTH_PLAN.md
Marius Mutu 1378ee1e6a Implement hybrid two-tier cache system with full monitoring and Telegram bot enhancements
Cache System (Backend):
- Implemented two-tier hybrid cache: L1 (in-memory) + L2 (SQLite)
- L1 cache: Fast dictionary-based with 5-minute TTL for hot data
- L2 cache: Persistent SQLite with 1-hour TTL for warm data
- Cache decorator with automatic tier management and fallback
- Cache key generation with per-user isolation
- Event monitoring system for cache statistics
- Cache benchmarking utilities for performance testing
- Added cache management endpoints: /api/cache/stats, /api/cache/clear, /api/cache/benchmark
- Cache configuration via environment variables (CACHE_ENABLED, CACHE_L1_TTL, etc.)

Backend Services:
- Updated dashboard_service to use @cached decorator with request context
- Added cache support to invoice_service and treasury_service
- Integrated cache manager into main.py with lifespan events
- Added Request parameter to service methods for cache metadata

Frontend Enhancements:
- New CacheStatsView.vue for real-time cache monitoring dashboard
- Cache store (cacheStore.js) for state management
- Updated router to include /cache-stats route
- Navigation updates in DashboardHeader and HamburgerMenu
- Cache stats accessible from main navigation

Telegram Bot Improvements:
- Enhanced formatters with YTD comparison data
- Improved menu navigation and button layout
- Better error handling and user feedback
- Bot startup improvements with graceful shutdown

Auth & Middleware:
- Enhanced middleware with cache metadata injection
- Improved request state handling for cache source tracking

Development:
- Updated start-dev.sh with better error handling
- Added TELEGRAM_EMAIL_AUTH_PLAN.md documentation
- Updated requirements.txt with aiosqlite for async SQLite

Performance:
- L1 cache provides <1ms response for hot data
- L2 cache provides ~5ms response for warm data
- Database queries only for cold data or cache misses
- Cache hit rates tracked and displayed in real-time

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 22:42:00 +02:00

58 KiB

Plan: Autentificare Telegram Bot prin Email + Parolă (2FA)

Data: 2025-11-07 Autor: Claude Code Status: Planificat - Neîmplementat


🎯 Obiectiv

Implementare autentificare alternativă email + parolă pentru Telegram Bot, în paralel cu metoda actuală (cod din web app). Ambele metode vor fi disponibile pentru toți utilizatorii.

Cerințe cheie:

  • Minimal invaziv - nu modifica logica existentă
  • Ambele metode de autentificare în paralel
  • 2FA real: email possession + parolă Oracle
  • Simplu de testat cu un singur utilizator (mmarius28@gmail.com)

📊 Context: Sistemul Actual

Metoda Actuală de Autentificare (rămâne neschimbată)

1. User se autentifică în web app (username + parolă)
2. User cere linking Telegram → backend generează cod 8-char
3. Backend salvează cod în telegram-bot via POST /internal/save-code
4. User trimite /start ABC123XY în Telegram
5. Bot validează codul și auto-linkează (fără parolă din nou)
6. User autentificat în bot

Caracteristici:

  • Nu necesită email
  • Necesită acces la web app
  • Auto-linking fără re-introducere parolă
  • Cod expiră în 15 minute

🔄 Noul Flux de Autentificare (Email + Parolă)

Flow Complet

┌─────────────────────────────────────────────────────────────┐
│ 1. User: /login SAU apasă buton "🔐 Login cu Email"        │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Bot: "📧 Introdu adresa ta de email Oracle:"            │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. User: mmarius28@gmail.com                                │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 4. Bot:                                                      │
│    - Verifică email în Oracle UTILIZATORI table             │
│    - Generează cod 6-digit random                           │
│    - Salvează în email_auth_codes (expiry 5 min)           │
│    - Trimite email SMTP: "Codul tău: 123456"               │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Bot: "✉️ Cod trimis pe email. Introdu codul (5 min):"   │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 6. User: 123456                                              │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 7. Bot:                                                      │
│    - Validează cod în email_auth_codes                      │
│    - Verifică expiry (5 minute)                             │
│    - Marchează cod ca "used"                                │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 8. Bot: "🔐 Introdu parola ta Oracle:"                      │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 9. User: parola_mea (mesaj va fi șters automat)            │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 10. Bot → Backend:                                           │
│     POST /api/telegram/auth/login-with-email                │
│     {                                                        │
│       "email": "mmarius28@gmail.com",                       │
│       "password": "parola_mea",                             │
│       "telegram_user_id": 123456789                         │
│     }                                                        │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 11. Backend:                                                 │
│     - Găsește username din email în UTILIZATORI             │
│     - Verifică parolă: pack_drepturi.verificautilizator()   │
│     - Generează JWT tokens (access + refresh)               │
│     - Returnează tokens + user data                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 12. Bot:                                                     │
│     - Salvează JWT în telegram_users table                  │
│     - Linkează telegram_user_id cu oracle_username          │
│     - Șterge mesajele cu parolă                             │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 13. Bot: "✅ Autentificat cu succes! Folosește /help"      │
└─────────────────────────────────────────────────────────────┘

Conversation States (ConversationHandler)

AWAITING_EMAIL = 1      # Așteaptă email de la user
AWAITING_CODE = 2       # Așteaptă cod din email
AWAITING_PASSWORD = 3   # Așteaptă parolă Oracle

📦 Componente Noi (Arhitectură)

1. Email Service (SMTP Client)

Fișier NOU: reports-app/telegram-bot/app/utils/email_service.py

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os

class EmailService:
    def __init__(self):
        self.smtp_host = os.getenv("SMTP_HOST", "mail.romfast.ro")
        self.smtp_port = int(os.getenv("SMTP_PORT", "587"))
        self.smtp_user = os.getenv("SMTP_USER", "ups@romfast.ro")
        self.smtp_password = os.getenv("SMTP_PASSWORD", "#Ups2020#")
        self.from_email = os.getenv("SMTP_FROM_EMAIL", "ups@romfast.ro")
        self.from_name = os.getenv("SMTP_FROM_NAME", "ROA2WEB")

    async def send_auth_code(self, to_email: str, code: str, username: str) -> bool:
        """
        Trimite cod de autentificare pe email
        Returns: True dacă email trimis cu succes
        """
        subject = "Codul tău de autentificare ROA2WEB"

        html_body = f"""
        <html>
        <body style="font-family: Arial, sans-serif;">
            <h2>🔐 Autentificare Telegram Bot</h2>
            <p>Salut <strong>{username}</strong>,</p>
            <p>Codul tău de autentificare este:</p>
            <h1 style="background-color: #f0f0f0; padding: 20px; text-align: center; letter-spacing: 5px;">
                {code}
            </h1>
            <p><strong>Codul expiră în 5 minute.</strong></p>
            <p>Dacă nu ai solicitat acest cod, te rugăm să ignori acest email.</p>
            <hr>
            <p style="color: #666; font-size: 12px;">
                ROA2WEB - ERP Reports Application
            </p>
        </body>
        </html>
        """

        # Implementare SMTP cu error handling

Funcții:

  • send_auth_code(email, code, username) - trimite cod pe email
  • _send_email(to, subject, html_body) - helper SMTP generic
  • Error handling cu retry logic (3 încercări)

2. Tabela SQLite Nouă: email_auth_codes

Locație: reports-app/telegram-bot/app/db/database.py

Schema SQL:

CREATE TABLE IF NOT EXISTS email_auth_codes (
    code TEXT PRIMARY KEY,              -- 6-digit numeric code (e.g., "123456")
    email TEXT NOT NULL,                -- Email utilizator din Oracle
    oracle_username TEXT NOT NULL,      -- Username Oracle asociat
    telegram_user_id INTEGER NOT NULL,  -- Telegram user ID care a solicitat
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NOT NULL,      -- Current time + 5 minute
    used BOOLEAN DEFAULT 0,             -- 0 = nefolosit, 1 = folosit
    used_at TIMESTAMP,                  -- Timestamp când a fost folosit

    -- Index pentru query performance
    INDEX idx_email (email),
    INDEX idx_expires_at (expires_at),
    INDEX idx_telegram_user_id (telegram_user_id)
);

Operații CRUD (în app/db/operations.py):

async def create_email_auth_code(code: str, email: str, username: str, telegram_user_id: int) -> bool
async def get_email_auth_code(code: str) -> Optional[Dict]
async def mark_email_code_used(code: str) -> bool
async def delete_expired_email_codes() -> int
async def get_pending_email_code_for_user(telegram_user_id: int) -> Optional[Dict]

Caracteristici:

  • Cod 6-digit random numeric (000000 - 999999)
  • Expirare 5 minute
  • One-time use (marcat ca used=1)
  • Auto-cleanup de către job-ul existent (hourly)

3. Backend Endpoint NOU: POST /api/telegram/auth/login-with-email

Locație: reports-app/backend/app/routers/telegram.py

Request Schema:

class TelegramEmailLoginRequest(BaseModel):
    email: EmailStr
    password: str
    telegram_user_id: int

class TelegramEmailLoginResponse(BaseModel):
    success: bool
    access_token: str
    refresh_token: str
    token_type: str = "bearer"
    user_id: int
    username: str
    companies: List[CompanyInfo]
    message: str

Endpoint Logic:

@router.post("/auth/login-with-email", response_model=TelegramEmailLoginResponse)
async def login_with_email(request: TelegramEmailLoginRequest):
    """
    Autentificare Telegram prin email + parolă

    Flow:
    1. Caută username în Oracle UTILIZATORI by email
    2. Verifică parolă prin pack_drepturi.verificautilizator(username, password)
    3. Dacă valid: generează JWT tokens
    4. Returnează tokens + user data
    """

    async with oracle_pool.get_connection() as connection:
        # 1. Find username by email
        cursor = connection.cursor()
        cursor.execute("""
            SELECT ID_UTIL, UTILIZATOR
            FROM CONTAFIN_ORACLE.UTILIZATORI
            WHERE UPPER(EMAIL) = UPPER(:email)
        """, {"email": request.email})

        user_row = cursor.fetchone()
        if not user_row:
            raise HTTPException(status_code=404, detail="Email not found")

        user_id, username = user_row

        # 2. Verify password via stored procedure
        cursor.execute("""
            SELECT pack_drepturi.verificautilizator(:username, :password)
            FROM DUAL
        """, {"username": username, "password": request.password})

        result = cursor.fetchone()[0]
        if result == -1:
            raise HTTPException(status_code=401, detail="Invalid password")

        # 3. Get user companies
        companies = get_user_companies(user_id, connection)

        # 4. Generate JWT tokens
        access_token = create_access_token(...)
        refresh_token = create_refresh_token(...)

        return TelegramEmailLoginResponse(
            success=True,
            access_token=access_token,
            refresh_token=refresh_token,
            user_id=user_id,
            username=username,
            companies=companies,
            message="Authentication successful"
        )

Securitate:

  • Rate limiting: 5 requests / 5 minutes per telegram_user_id
  • Password validation prin Oracle stored procedure (nu stocăm parole)
  • HTTPS required în producție
  • Logging pentru failed attempts

4. Bot Handlers NOI: /login + ConversationHandler

Fișier NOU: reports-app/telegram-bot/app/bot/email_handlers.py

Command: /login

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, MessageHandler, filters

# Conversation states
AWAITING_EMAIL, AWAITING_CODE, AWAITING_PASSWORD = range(3)

async def login_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Handler pentru /login command
    Inițiază conversation pentru email auth
    """
    user = update.effective_user

    # Check dacă e deja autentificat
    if await is_user_authenticated(user.id):
        await update.message.reply_text(
            "✅ Ești deja autentificat!\n"
            "Folosește /unlink pentru a te deautentifica."
        )
        return ConversationHandler.END

    # Afișează metode de autentificare
    keyboard = [
        [InlineKeyboardButton("🔐 Login cu Email", callback_data="email_login")],
        [InlineKeyboardButton("🌐 Login din Web App", callback_data="web_login")],
        [InlineKeyboardButton("❌ Anulează", callback_data="cancel")]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)

    await update.message.reply_text(
        "🔑 Alege metoda de autentificare:\n\n"
        "📧 **Email + Parolă**: Primești cod pe email, apoi introduci parola\n"
        "🌐 **Web App**: Generează cod din aplicația web",
        reply_markup=reply_markup
    )

    return AWAITING_EMAIL

async def email_login_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Callback pentru butonul 'Login cu Email'"""
    query = update.callback_query
    await query.answer()

    await query.edit_message_text(
        "📧 **Autentificare prin Email**\n\n"
        "Te rugăm să introduci adresa ta de email Oracle:\n"
        "(Exemplu: nume.prenume@companie.ro)"
    )

    return AWAITING_EMAIL

async def receive_email(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Handler pentru primirea email-ului
    """
    email = update.message.text.strip()
    user_id = update.effective_user.id

    # Validare format email
    if not is_valid_email(email):
        await update.message.reply_text(
            "❌ Email invalid. Te rugăm să introduci o adresă de email validă."
        )
        return AWAITING_EMAIL

    # Verifică email în Oracle + trimite cod
    try:
        # 1. Verifică email în Oracle UTILIZATORI
        username = await verify_email_in_oracle(email)
        if not username:
            await update.message.reply_text(
                "❌ Email-ul nu este înregistrat în sistem.\n"
                "Contactează administratorul pentru a-ți adăuga email-ul."
            )
            return ConversationHandler.END

        # 2. Generează cod 6-digit
        code = generate_6digit_code()

        # 3. Salvează în email_auth_codes
        await save_email_auth_code(
            code=code,
            email=email,
            username=username,
            telegram_user_id=user_id
        )

        # 4. Trimite email
        email_sent = await email_service.send_auth_code(email, code, username)

        if not email_sent:
            await update.message.reply_text(
                "❌ Eroare la trimiterea email-ului. Te rugăm să încerci din nou."
            )
            return ConversationHandler.END

        # 5. Salvează email în context
        context.user_data['pending_email'] = email
        context.user_data['pending_username'] = username

        await update.message.reply_text(
            f"✉️ **Cod trimis pe {email}**\n\n"
            "Verifică inbox-ul (și spam) și introdu codul de 6 cifre.\n"
            "⏱ Codul expiră în **5 minute**.\n\n"
            "Scrie /cancel pentru a anula."
        )

        return AWAITING_CODE

    except Exception as e:
        logger.error(f"Email login error: {e}")
        await update.message.reply_text(
            "❌ Eroare internă. Te rugăm să încerci din nou mai târziu."
        )
        return ConversationHandler.END

async def receive_code(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Handler pentru primirea codului din email
    """
    code = update.message.text.strip()
    user_id = update.effective_user.id

    # Validare format cod (6 digits)
    if not code.isdigit() or len(code) != 6:
        await update.message.reply_text(
            "❌ Cod invalid. Introdu cele 6 cifre din email."
        )
        return AWAITING_CODE

    # Verifică cod în DB
    try:
        code_data = await get_email_auth_code(code)

        if not code_data:
            await update.message.reply_text(
                "❌ Cod invalid sau expirat. Te rugăm să reîncepi cu /login"
            )
            return ConversationHandler.END

        # Verificări
        if code_data['used']:
            await update.message.reply_text(
                "❌ Cod deja folosit. Te rugăm să reîncepi cu /login"
            )
            return ConversationHandler.END

        if code_data['telegram_user_id'] != user_id:
            await update.message.reply_text(
                "❌ Codul nu îți aparține."
            )
            return ConversationHandler.END

        if datetime.now() > code_data['expires_at']:
            await update.message.reply_text(
                "❌ Codul a expirat. Te rugăm să reîncepi cu /login"
            )
            return ConversationHandler.END

        # Marchează cod ca folosit
        await mark_email_code_used(code)

        # Salvează username în context
        context.user_data['verified_username'] = code_data['oracle_username']
        context.user_data['verified_email'] = code_data['email']

        await update.message.reply_text(
            "✅ **Cod validat cu succes!**\n\n"
            "🔐 Acum introdu parola ta Oracle:\n"
            "(Parola va fi ștearsă automat după verificare)"
        )

        return AWAITING_PASSWORD

    except Exception as e:
        logger.error(f"Code validation error: {e}")
        await update.message.reply_text(
            "❌ Eroare la validarea codului. Te rugăm să încerci din nou."
        )
        return ConversationHandler.END

async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Handler pentru primirea parolei
    """
    password = update.message.text.strip()
    user_id = update.effective_user.id

    # Șterge imediat mesajul cu parola
    try:
        await update.message.delete()
    except:
        pass

    username = context.user_data.get('verified_username')
    email = context.user_data.get('verified_email')

    if not username or not email:
        await update.effective_chat.send_message(
            "❌ Sesiune expirată. Te rugăm să reîncepi cu /login"
        )
        return ConversationHandler.END

    # Trimite loading message
    loading_msg = await update.effective_chat.send_message(
        "⏳ Verificare credențiale..."
    )

    try:
        # Call backend endpoint
        response = await backend_client.login_with_email(
            email=email,
            password=password,
            telegram_user_id=user_id
        )

        if not response['success']:
            await loading_msg.edit_text(
                "❌ Parolă incorectă. Te rugăm să reîncepi cu /login"
            )
            return ConversationHandler.END

        # Salvează user în telegram_users
        await save_telegram_user(
            telegram_user_id=user_id,
            username=update.effective_user.username,
            first_name=update.effective_user.first_name,
            last_name=update.effective_user.last_name,
            oracle_username=response['username'],
            jwt_token=response['access_token'],
            jwt_refresh_token=response['refresh_token']
        )

        # Success message
        companies_list = "\n".join([f"• {c['name']}" for c in response['companies'][:5]])

        await loading_msg.edit_text(
            f"✅ **Autentificat cu succes!**\n\n"
            f"👤 Utilizator: **{response['username']}**\n"
            f"🏢 Companii disponibile: {len(response['companies'])}\n\n"
            f"{companies_list}\n\n"
            f"Folosește /help pentru comenzi disponibile."
        )

        # Clear context
        context.user_data.clear()

        return ConversationHandler.END

    except Exception as e:
        logger.error(f"Password verification error: {e}")
        await loading_msg.edit_text(
            "❌ Eroare la autentificare. Te rugăm să încerci din nou cu /login"
        )
        return ConversationHandler.END

async def cancel_login(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Cancel conversation"""
    context.user_data.clear()
    await update.message.reply_text("❌ Autentificare anulată.")
    return ConversationHandler.END

# ConversationHandler setup
email_login_handler = ConversationHandler(
    entry_points=[
        CommandHandler('login', login_command),
        CallbackQueryHandler(email_login_callback, pattern='^email_login$')
    ],
    states={
        AWAITING_EMAIL: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_email)],
        AWAITING_CODE: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_code)],
        AWAITING_PASSWORD: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_password)],
    },
    fallbacks=[
        CommandHandler('cancel', cancel_login),
        CallbackQueryHandler(cancel_login, pattern='^cancel$')
    ],
    conversation_timeout=300  # 5 minute timeout
)

Handler Registration (în app/bot/handlers.py):

from app.bot.email_handlers import email_login_handler

def setup_handlers(application):
    # ... existing handlers ...

    # Email login handler
    application.add_handler(email_login_handler)

5. Backend API Client Method

Locație: reports-app/telegram-bot/app/api/client.py

class BackendAPIClient:
    # ... existing methods ...

    async def login_with_email(
        self,
        email: str,
        password: str,
        telegram_user_id: int
    ) -> dict:
        """
        Login via email + password
        """
        try:
            response = await self.client.post(
                f"{self.base_url}/api/telegram/auth/login-with-email",
                json={
                    "email": email,
                    "password": password,
                    "telegram_user_id": telegram_user_id
                }
            )
            response.raise_for_status()
            return response.json()
        except httpx.HTTPError as e:
            logger.error(f"Email login failed: {e}")
            raise

🔧 Environment Variables

Backend .env (nu necesită modificări - doar pentru referință)

# Existente (nu modificăm)
ORACLE_USER=CONTAFIN_ORACLE
ORACLE_PASSWORD=your_password
ORACLE_HOST=localhost
ORACLE_PORT=1526
ORACLE_SID=ROA
JWT_SECRET_KEY=your_secret_key
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=30

Telegram Bot .env (ADAUGĂ ACESTEA)

# Existing
TELEGRAM_BOT_TOKEN=your_bot_token
BACKEND_URL=http://localhost:8000
DATABASE_PATH=data/telegram_bot.db

# NEW: SMTP Configuration
SMTP_HOST=mail.romfast.ro
SMTP_PORT=587
SMTP_USER=ups@romfast.ro
SMTP_PASSWORD=#Ups2020#
SMTP_FROM_EMAIL=ups@romfast.ro
SMTP_FROM_NAME=ROA2WEB
SMTP_USE_TLS=true

# NEW: Email Auth Settings
EMAIL_CODE_EXPIRY_MINUTES=5
EMAIL_CODE_LENGTH=6
MAX_EMAIL_ATTEMPTS_PER_HOUR=3

🔒 Securitate & Validări

1. Email Validation

import re

def is_valid_email(email: str) -> bool:
    """Validare format email"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

async def verify_email_in_oracle(email: str) -> Optional[str]:
    """
    Verifică dacă email există în Oracle UTILIZATORI
    Returns: username dacă există, None altfel
    """
    async with oracle_pool.get_connection() as connection:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT UTILIZATOR
            FROM CONTAFIN_ORACLE.UTILIZATORI
            WHERE UPPER(EMAIL) = UPPER(:email)
            AND ACTIV = 1
        """, {"email": email})

        row = cursor.fetchone()
        return row[0] if row else None

2. Code Generation & Storage

import random
import string
from datetime import datetime, timedelta

def generate_6digit_code() -> str:
    """Generează cod random 6-digit"""
    return ''.join(random.choices(string.digits, k=6))

async def save_email_auth_code(
    code: str,
    email: str,
    username: str,
    telegram_user_id: int
) -> bool:
    """Salvează cod în DB cu expiry 5 minute"""
    expires_at = datetime.now() + timedelta(minutes=5)

    async with get_db_connection() as db:
        await db.execute("""
            INSERT INTO email_auth_codes
            (code, email, oracle_username, telegram_user_id, expires_at)
            VALUES (?, ?, ?, ?, ?)
        """, (code, email, username, telegram_user_id, expires_at))
        await db.commit()

    return True

3. Rate Limiting

from collections import defaultdict
from datetime import datetime, timedelta

# In-memory rate limiter (sau Redis în producție)
email_attempts = defaultdict(list)

async def check_rate_limit(identifier: str, max_attempts: int = 3, window_minutes: int = 60) -> bool:
    """
    Rate limiting pentru email requests
    identifier poate fi: email sau telegram_user_id
    """
    now = datetime.now()
    cutoff = now - timedelta(minutes=window_minutes)

    # Curăță attempts vechi
    email_attempts[identifier] = [
        attempt for attempt in email_attempts[identifier]
        if attempt > cutoff
    ]

    # Verifică limita
    if len(email_attempts[identifier]) >= max_attempts:
        return False  # Rate limit exceeded

    # Adaugă attempt nou
    email_attempts[identifier].append(now)
    return True  # OK to proceed

4. Password Security

async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handler cu securitate mărite pentru parolă"""
    password = update.message.text.strip()

    # 1. Șterge IMEDIAT mesajul cu parola
    try:
        await update.message.delete()
    except Exception as e:
        logger.warning(f"Could not delete password message: {e}")

    # 2. NU loga parola niciodată
    logger.info(f"Password received for user {update.effective_user.id}")

    # 3. Verifică parola prin backend (Oracle)
    # ... (vezi cod de mai sus)

    # 4. Șterge parola din memorie
    del password

5. Auto-Cleanup Job

async def cleanup_expired_codes():
    """Job periodic pentru curățarea codurilor expirate"""
    while True:
        try:
            async with get_db_connection() as db:
                # Șterge email codes expirate
                await db.execute("""
                    DELETE FROM email_auth_codes
                    WHERE expires_at < ?
                    OR (used = 1 AND used_at < ?)
                """, (
                    datetime.now(),
                    datetime.now() - timedelta(days=1)  # Cleanup used codes după 1 zi
                ))
                await db.commit()

                deleted = db.total_changes
                logger.info(f"Cleaned up {deleted} expired email auth codes")

        except Exception as e:
            logger.error(f"Cleanup job error: {e}")

        # Run every hour
        await asyncio.sleep(3600)

# Start cleanup job în main.py
asyncio.create_task(cleanup_expired_codes())

📝 Fișiere Modificate/Create

CREATE (6 fișiere noi):

  1. reports-app/telegram-bot/app/utils/email_service.py

    • SMTP client pentru trimitere email-uri
    • Funcție: send_auth_code(email, code, username)
    • Error handling + retry logic
  2. reports-app/telegram-bot/app/auth/email_auth.py

    • Logică autentificare email
    • Funcții: verify_email_in_oracle(), generate_6digit_code(), check_rate_limit()
  3. reports-app/telegram-bot/app/bot/email_handlers.py

    • ConversationHandler pentru /login
    • States: AWAITING_EMAIL → AWAITING_CODE → AWAITING_PASSWORD
    • Handlers: receive_email(), receive_code(), receive_password()
  4. reports-app/backend/app/schemas/telegram_email_auth.py

    • Pydantic schemas pentru email auth
    • TelegramEmailLoginRequest, TelegramEmailLoginResponse
  5. reports-app/telegram-bot/tests/test_email_auth.py

    • Unit tests pentru email auth flow
    • Mock SMTP, Oracle, backend API
  6. TELEGRAM_EMAIL_AUTH_PLAN.md

    • Acest document (plan detaliat)

MODIFY (6 fișiere existente):

  1. reports-app/telegram-bot/app/db/database.py

    • Adaugă: Schema tabela email_auth_codes
    • Linie: ~40-60 (în funcția init_database())
  2. reports-app/telegram-bot/app/db/operations.py

    • Adaugă: CRUD operations pentru email_auth_codes
    • Funcții noi: create_email_auth_code(), get_email_auth_code(), mark_email_code_used(), etc.
    • Linii: ~200-300 (la final de fișier)
  3. reports-app/telegram-bot/app/bot/handlers.py

    • Adaugă: Import și register email_login_handler
    • Linie: ~10 (import), ~150 (register în setup_handlers())
    • Modificare: Update /start message pentru a menționa /login
  4. reports-app/backend/app/routers/telegram.py

    • Adaugă: Endpoint POST /auth/login-with-email
    • Linii: ~200-300 (la final de fișier)
    • Import: Adaugă schema TelegramEmailLoginRequest/Response
  5. reports-app/telegram-bot/app/api/client.py

    • Adaugă: Method login_with_email(email, password, telegram_user_id)
    • Linii: ~150-180 (în clasa BackendAPIClient)
  6. reports-app/telegram-bot/.env

    • Adaugă: SMTP configuration variables (vezi secțiunea Environment Variables)

NO TOUCH (rămân 100% neschimbate):

  • Fluxul actual de linking (web app → cod → /start)
  • Tabele existente: telegram_users, telegram_auth_codes, telegram_sessions
  • Endpoint-uri existente: /generate-code, /verify-user, /refresh-token
  • Handler-e existente: /start [code], /companies, /dashboard, /facturi, etc.
  • Toate comenzile Telegram existente (funcționalitate păstrată 100%)

🧪 Testing Strategy

1. Unit Tests

Fișier: reports-app/telegram-bot/tests/test_email_auth.py

import pytest
from unittest.mock import AsyncMock, patch
from app.auth.email_auth import verify_email_in_oracle, generate_6digit_code, check_rate_limit
from app.utils.email_service import EmailService

@pytest.mark.asyncio
async def test_generate_6digit_code():
    """Test generare cod 6-digit"""
    code = generate_6digit_code()
    assert len(code) == 6
    assert code.isdigit()

@pytest.mark.asyncio
async def test_verify_email_in_oracle_success():
    """Test verificare email valid în Oracle"""
    with patch('app.auth.email_auth.oracle_pool') as mock_pool:
        # Mock Oracle response
        mock_cursor = AsyncMock()
        mock_cursor.fetchone.return_value = ("test_user",)

        username = await verify_email_in_oracle("test@example.com")
        assert username == "test_user"

@pytest.mark.asyncio
async def test_verify_email_not_found():
    """Test email inexistent în Oracle"""
    with patch('app.auth.email_auth.oracle_pool') as mock_pool:
        mock_cursor = AsyncMock()
        mock_cursor.fetchone.return_value = None

        username = await verify_email_in_oracle("notfound@example.com")
        assert username is None

@pytest.mark.asyncio
async def test_rate_limiting():
    """Test rate limiting pentru email requests"""
    identifier = "test_user_123"

    # First 3 attempts should pass
    for i in range(3):
        result = await check_rate_limit(identifier, max_attempts=3, window_minutes=60)
        assert result is True

    # 4th attempt should fail
    result = await check_rate_limit(identifier, max_attempts=3, window_minutes=60)
    assert result is False

@pytest.mark.asyncio
async def test_email_service_send_code():
    """Test trimitere email cu cod"""
    email_service = EmailService()

    with patch('app.utils.email_service.smtplib.SMTP') as mock_smtp:
        result = await email_service.send_auth_code(
            to_email="test@example.com",
            code="123456",
            username="test_user"
        )

        assert result is True
        assert mock_smtp.called

2. Integration Tests

@pytest.mark.asyncio
async def test_full_email_auth_flow():
    """Test complet: email → cod → parolă → JWT"""

    # 1. Request email code
    email = "mmarius28@gmail.com"
    telegram_user_id = 123456789

    with patch('app.auth.email_auth.verify_email_in_oracle') as mock_verify:
        mock_verify.return_value = "marius_user"

        # Generate code
        code = generate_6digit_code()
        await save_email_auth_code(code, email, "marius_user", telegram_user_id)

        # 2. Validate code
        code_data = await get_email_auth_code(code)
        assert code_data is not None
        assert code_data['email'] == email
        assert code_data['used'] is False

        # 3. Mock backend login
        with patch('app.api.client.BackendAPIClient.login_with_email') as mock_login:
            mock_login.return_value = {
                'success': True,
                'access_token': 'test_jwt_token',
                'refresh_token': 'test_refresh_token',
                'username': 'marius_user',
                'user_id': 42,
                'companies': []
            }

            # Verify password
            response = await backend_client.login_with_email(
                email=email,
                password="test_password",
                telegram_user_id=telegram_user_id
            )

            assert response['success'] is True
            assert 'access_token' in response

3. Manual Testing Checklist

Fișier: reports-app/telegram-bot/tests/MANUAL_EMAIL_AUTH_TESTING.md

# Manual Testing Checklist - Email Authentication

## Prerequisites
- [ ] Backend running on port 8000
- [ ] Telegram bot running
- [ ] SMTP credentials configured in .env
- [ ] Test email: mmarius28@gmail.com exists in Oracle UTILIZATORI table

## Test Cases

### 1. Successful Authentication Flow
- [ ] Start bot: `/start`
- [ ] Type: `/login`
- [ ] Select "🔐 Login cu Email"
- [ ] Enter email: `mmarius28@gmail.com`
- [ ] Verify email received with 6-digit code
- [ ] Enter code from email
- [ ] Enter Oracle password
- [ ] Verify success message with companies list
- [ ] Test command: `/companies` (should work)

### 2. Invalid Email
- [ ] `/login`
- [ ] Enter email: `nonexistent@example.com`
- [ ] Verify error message: "Email-ul nu este înregistrat"

### 3. Expired Code
- [ ] `/login` → enter valid email
- [ ] Wait 6 minutes (code expiry = 5 min)
- [ ] Enter code
- [ ] Verify error: "Cod expirat"

### 4. Wrong Code
- [ ] `/login` → enter valid email
- [ ] Enter wrong code: `999999`
- [ ] Verify error: "Cod invalid"

### 5. Wrong Password
- [ ] `/login` → enter email → enter valid code
- [ ] Enter wrong password
- [ ] Verify error: "Parolă incorectă"

### 6. Rate Limiting
- [ ] `/login` → enter email (attempt 1)
- [ ] `/cancel`
- [ ] `/login` → enter email (attempt 2)
- [ ] `/cancel`
- [ ] `/login` → enter email (attempt 3)
- [ ] `/cancel`
- [ ] `/login` → enter email (attempt 4)
- [ ] Verify error: "Prea multe încercări. Încearcă în 60 minute"

### 7. Password Message Deletion
- [ ] `/login` → enter email → enter code
- [ ] Enter password
- [ ] Verify password message is deleted immediately
- [ ] Check chat history - password should not be visible

### 8. Cancel Flow
- [ ] `/login`
- [ ] `/cancel` (should cancel and clear state)

### 9. Already Authenticated
- [ ] Complete successful login
- [ ] Type `/login` again
- [ ] Verify message: "Ești deja autentificat"

### 10. Parallel Method (Web App Linking) Still Works
- [ ] Login to web app: http://localhost:3000
- [ ] Generate Telegram linking code
- [ ] Use `/start ABC123XY` in Telegram
- [ ] Verify linking works as before (OLD method)

### 11. Database Cleanup
- [ ] Create expired codes (manually set expires_at in past)
- [ ] Wait for hourly cleanup job
- [ ] Verify expired codes deleted from `email_auth_codes` table

📊 Estimare Efort & Timeline

Breakdown Detaliat

Componentă Efort (ore) Detalii
Backend
→ Endpoint /login-with-email 1.5h Logic + Oracle queries + JWT
→ Schema Pydantic 0.5h Request/Response models
→ Rate limiting 0.5h Middleware sau decorator
→ Testing backend 0.5h Unit tests endpoint
Telegram Bot
→ Email service (SMTP) 1.5h Client SMTP + templates + retry
→ DB schema + operations 1h Tabela nouă + CRUD
→ Email auth logic 1h Verificare email, cod generation
→ ConversationHandler 2h States + handlers + callbacks
→ API client method 0.5h Method nou în BackendAPIClient
→ Testing bot 1h Unit + integration tests
Integration
→ End-to-end testing 1h Flow complet manual
→ Bug fixes 1h Edge cases
Documentation
→ Update README 0.5h Secțiune nouă pentru email auth
→ Update TELEGRAM_COMMANDS.md 0.5h Documentare /login
TOTAL ~13h ~2 zile lucru (6-7h/zi)

Timeline Recomandat

Ziua 1 (6-7h):

  1. Setup environment (SMTP config) - 0.5h
  2. Backend: Endpoint + schema - 2h
  3. Telegram Bot: Email service - 1.5h
  4. Telegram Bot: DB schema + operations - 1h
  5. Testing preliminar - 1h

Ziua 2 (6-7h):

  1. Telegram Bot: ConversationHandler - 2h
  2. Email auth logic - 1h
  3. Integration - 1h
  4. End-to-end testing - 1.5h
  5. Documentation - 0.5h
  6. Bug fixes - 1h

Avantaje Plan

Avantaj Detalii
Minimal Invaziv 0 modificări la logica existentă, doar adăugiri
Ambele Metode Users pot alege: email+parolă SAU web app linking
Backward Compatible Utilizatori fără email folosesc metoda actuală
2FA Real Email possession (cod) + parolă Oracle = 2 factori
Simplu de Testat Un singur utilizator cu email (mmarius28@gmail.com)
Extensibil Ușor de activat pentru alți useri când adaugi email-uri
Securitate Mărite Rate limiting, code expiry, password verification în Oracle
Auto-Cleanup Job existent curăță automat coduri expirate
User-Friendly Conversation flow natural în Telegram
Error Handling Mesaje clare pentru fiecare caz de eroare

🚀 Ordine Implementare (Step-by-Step)

Step 1: Environment Setup (15 min)

  1. Adaugă SMTP credentials în .env:
cd reports-app/telegram-bot/
echo "SMTP_HOST=mail.romfast.ro" >> .env
echo "SMTP_PORT=587" >> .env
echo "SMTP_USER=ups@romfast.ro" >> .env
echo "SMTP_PASSWORD=#Ups2020#" >> .env
echo "SMTP_FROM_EMAIL=ups@romfast.ro" >> .env
echo "SMTP_FROM_NAME=ROA2WEB" >> .env
  1. Install dependencies:
pip install aiosmtplib  # Async SMTP client

Step 2: Backend - Endpoint (2h)

  1. Create schema: backend/app/schemas/telegram_email_auth.py
  2. Add endpoint în backend/app/routers/telegram.py
  3. Test cu Postman/curl

Step 3: Telegram Bot - Infrastructure (2.5h)

  1. Create app/utils/email_service.py (SMTP client)
  2. Update app/db/database.py (add email_auth_codes table)
  3. Update app/db/operations.py (CRUD operations)
  4. Test DB operations

Step 4: Telegram Bot - Auth Logic (1h)

  1. Create app/auth/email_auth.py
  2. Implement: verify_email_in_oracle(), generate_6digit_code(), check_rate_limit()
  3. Unit tests

Step 5: Telegram Bot - Handlers (2h)

  1. Create app/bot/email_handlers.py
  2. Implement ConversationHandler
  3. Register în app/bot/handlers.py

Step 6: Backend API Client (30 min)

  1. Add login_with_email() method în app/api/client.py

Step 7: Integration Testing (1h)

  1. Start backend + bot
  2. Test flow complet cu mmarius28@gmail.com
  3. Fix bugs

Step 8: Comprehensive Testing (1.5h)

  1. Run all test cases from checklist
  2. Test edge cases
  3. Verify both methods work in parallel

Step 9: Documentation (30 min)

  1. Update README.md
  2. Update TELEGRAM_COMMANDS.md
  3. Create MANUAL_EMAIL_AUTH_TESTING.md

🐛 Troubleshooting Guide

Issue: Email nu ajunge

Cauze posibile:

  1. SMTP credentials incorecte
  2. Port blocat (587)
  3. Email în spam

Debugging:

# Test SMTP connection
import smtplib

try:
    server = smtplib.SMTP('mail.romfast.ro', 587)
    server.starttls()
    server.login('ups@romfast.ro', '#Ups2020#')
    print("✅ SMTP connection OK")
    server.quit()
except Exception as e:
    print(f"❌ SMTP error: {e}")

Issue: Cod invalid/expirat

Debugging:

-- Check code in DB
SELECT * FROM email_auth_codes WHERE code = '123456';

-- Check expiry
SELECT code, expires_at,
       datetime('now') as now,
       (julianday(expires_at) - julianday('now')) * 24 * 60 as minutes_remaining
FROM email_auth_codes
WHERE code = '123456';

Issue: Parolă incorectă (dar știu că e corectă)

Cauze posibile:

  1. Username găsit greșit din email
  2. Oracle stored procedure error

Debugging:

# Test Oracle auth direct
async with oracle_pool.get_connection() as connection:
    cursor = connection.cursor()

    # 1. Check email → username mapping
    cursor.execute("""
        SELECT UTILIZATOR, EMAIL
        FROM CONTAFIN_ORACLE.UTILIZATORI
        WHERE UPPER(EMAIL) = UPPER('mmarius28@gmail.com')
    """)
    print(cursor.fetchone())

    # 2. Test stored procedure
    cursor.execute("""
        SELECT pack_drepturi.verificautilizator('marius_user', 'parola_mea')
        FROM DUAL
    """)
    result = cursor.fetchone()[0]
    print(f"Auth result: {result}")  # -1 = fail, user_id = success

Issue: Rate limiting prea restrictiv

Quick fix:

# În email_auth.py, increase limits
MAX_EMAIL_ATTEMPTS = 5  # was 3
RATE_LIMIT_WINDOW_MINUTES = 30  # was 60

📚 Additional Documentation

Email Template (HTML)

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
        .code-box { background-color: #f4f4f4; border: 2px solid #667eea; padding: 30px; margin: 20px 0; text-align: center; border-radius: 10px; }
        .code { font-size: 36px; font-weight: bold; letter-spacing: 10px; color: #667eea; font-family: 'Courier New', monospace; }
        .content { padding: 20px; background: white; }
        .warning { background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }
        .footer { text-align: center; color: #666; font-size: 12px; padding: 20px; border-top: 1px solid #ddd; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🔐 ROA2WEB</h1>
            <p>Autentificare Telegram Bot</p>
        </div>

        <div class="content">
            <p>Salut <strong>{{username}}</strong>,</p>

            <p>Ai solicitat autentificarea în aplicația ROA2WEB Telegram Bot.</p>

            <div class="code-box">
                <p style="margin: 0; font-size: 14px; color: #666;">Codul tău de autentificare:</p>
                <p class="code">{{code}}</p>
            </div>

            <div class="warning">
                <strong>⏱️ Important:</strong> Acest cod expiră în <strong>5 minute</strong>.
            </div>

            <p>Introdu acest cod în conversația cu Telegram Bot, apoi vei fi solicitat să introduci parola.</p>

            <p style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;">
                <strong>Nu ai solicitat acest cod?</strong><br>
                Dacă nu ai inițiat această autentificare, te rugăm să ignori acest email.
                Nimeni nu va avea acces la contul tău fără parola ta.
            </p>
        </div>

        <div class="footer">
            <p><strong>ROA2WEB</strong> - ERP Reports Application</p>
            <p>Acest email a fost trimis automat. Te rugăm să nu răspunzi.</p>
        </div>
    </div>
</body>
</html>

Command Reference Update

Adaugă în TELEGRAM_COMMANDS.md:

## /login

**Descriere**: Autentificare în Telegram Bot prin email + parolă (2FA)

**Sintaxă**: `/login`

**Flow**:
1. Selectezi metoda de autentificare (Email sau Web App)
2. Pentru Email:
   - Introduci adresa de email Oracle
   - Primești cod pe email (6 cifre)
   - Introduci codul din email
   - Introduci parola Oracle
   - Primești confirmare și acces la comenzi

**Exemple**:

/login → Selectează "🔐 Login cu Email" → Introdu: mmarius28@gmail.com → Verifică email și introdu cod: 123456 → Introdu parola → Autentificat cu succes!


**Securitate**:
- Cod expiră în 5 minute
- Cod utilizabil o singură dată
- Parolă verificată în Oracle
- Mesajul cu parola este șters automat
- Rate limiting: max 3 încercări/oră

**Comandă alternativă**: Buton "🔐 Login cu Email" în meniu principal

🎓 Learning Points & Best Practices

1. De ce 6-digit code în loc de 8-char alphanumeric?

Răspuns:

  • Mai ușor de tastat pe mobile
  • Mai puțin prone la erori (0/O, 1/I confusion)
  • Suficient de securizat pentru 5 minute expiry
  • Standard în industrie (Google, GitHub, etc.)

2. De ce 5 minute expiry pentru cod?

Răspuns:

  • Balans între security și UX
  • Suficient timp pentru check email + type code
  • Redus attack window pentru code guessing
  • Similar cu alte aplicații 2FA

3. De ce șterge mesajul cu parola?

Răspuns:

  • Securitate: parola nu rămâne în chat history
  • Telegram API permite ștergerea mesajelor
  • Best practice în bot-uri care cer credențiale
  • Protecție dacă telefonul e compromis

4. De ce în-memory rate limiting și nu Redis?

Răspuns:

  • Simplitate: Nu adaugă dependency nou
  • Suficient pentru scale mic-mediu:
    • In-memory e OK pentru <1000 users concurrent
    • Planul curent: test cu 1 user, extend la 10-20 max
  • Upgrade path clar: Dacă scală, migrăm la Redis
  • Code unchanged: Interfața rămâne aceeași

5. De ce SQLite și nu PostgreSQL pentru bot DB?

Răspuns:

  • Consistency: Sistemul actual folosește SQLite
  • Zero infrastructure: No server setup needed
  • Sufficient performance: Telegram bot = low traffic
  • Easy backup: Single file (telegram_bot.db)
  • Deployment simplicity: Works în Windows Service

🔗 Integrare cu Sistemul Existent

Flow Diagram - Ambele Metode

                    ┌─────────────────────────────────┐
                    │   USER VREA SĂ SE AUTENTIFICE   │
                    │      ÎN TELEGRAM BOT            │
                    └─────────────┬───────────────────┘
                                  │
                                  ▼
                    ┌─────────────────────────────────┐
                    │     ALEGE METODA:               │
                    │  [Email + Parolă] sau [Web App] │
                    └─────────┬───────────┬───────────┘
                              │           │
                 ┌────────────┘           └────────────┐
                 │                                     │
                 ▼                                     ▼
    ┌────────────────────────┐         ┌──────────────────────────┐
    │   METODA NOUĂ          │         │   METODA EXISTENTĂ       │
    │   Email + Parolă       │         │   Web App Linking        │
    └────────────────────────┘         └──────────────────────────┘
                 │                                     │
                 ▼                                     ▼
    1. /login în Telegram          1. Login în web app (username+pass)
    2. Introdu email               2. Click "Link Telegram"
    3. Primești cod pe email       3. Backend generează cod 8-char
    4. Introdu cod în Telegram     4. Backend → POST /internal/save-code → Bot
    5. Introdu parolă              5. User: /start ABC123XY
    6. Bot verifică în Oracle      6. Bot auto-link (fără parolă din nou)
                 │                                     │
                 └──────────────┬──────────────────────┘
                                │
                                ▼
                ┌───────────────────────────────────┐
                │  USER AUTENTIFICAT ÎN BOT         │
                │  JWT tokens saved în telegram_users│
                └───────────────────────────────────┘
                                │
                                ▼
                ┌───────────────────────────────────┐
                │  COMENZI DISPONIBILE:              │
                │  /companies, /dashboard,           │
                │  /facturi, /trezorerie, etc.       │
                └───────────────────────────────────┘

Database Schema - Complete View

-- EXISTING TABLE (no changes)
CREATE TABLE telegram_users (
    telegram_user_id INTEGER PRIMARY KEY,
    username TEXT,
    first_name TEXT,
    last_name TEXT,
    oracle_username TEXT,
    jwt_token TEXT,
    jwt_refresh_token TEXT,
    token_expires_at TIMESTAMP,
    linked_at TIMESTAMP,
    last_active_at TIMESTAMP,
    is_active BOOLEAN
);

-- EXISTING TABLE (no changes)
CREATE TABLE telegram_auth_codes (
    code TEXT PRIMARY KEY,              -- 8-char code from web app
    telegram_user_id INTEGER,
    oracle_username TEXT,
    created_at TIMESTAMP,
    expires_at TIMESTAMP,
    used BOOLEAN DEFAULT 0,
    used_at TIMESTAMP
);

-- NEW TABLE (for email auth)
CREATE TABLE email_auth_codes (
    code TEXT PRIMARY KEY,              -- 6-digit code sent via email
    email TEXT NOT NULL,
    oracle_username TEXT NOT NULL,
    telegram_user_id INTEGER NOT NULL,
    created_at TIMESTAMP,
    expires_at TIMESTAMP,
    used BOOLEAN DEFAULT 0,
    used_at TIMESTAMP,

    INDEX idx_email (email),
    INDEX idx_telegram_user_id (telegram_user_id)
);

-- EXISTING TABLE (no changes)
CREATE TABLE telegram_sessions (
    session_id TEXT PRIMARY KEY,
    telegram_user_id INTEGER,
    active_company_id INTEGER,
    active_company_name TEXT,
    conversation_state TEXT,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    expires_at TIMESTAMP
);

Observații:

  • telegram_auth_codes = pentru web app linking (EXISTING)
  • email_auth_codes = pentru email auth (NEW)
  • Ambele tabele coexistă, fiecare pentru flow-ul propriu
  • telegram_users = comună pentru ambele metode (linked users)

🚦 Success Criteria

Implementarea e considerată SUCCESS dacă:

  • User poate să facă /login în Telegram
  • User poate introduce email și primește cod pe email
  • User poate introduce cod din email (valid 5 min)
  • User poate introduce parola Oracle
  • Bot verifică credențialele în Oracle și generează JWT
  • User e autentificat și poate folosi comenzi (/companies, /dashboard, etc.)
  • Metoda veche (web app → /start cod) funcționează în continuare
  • Ambele metode pot fi folosite de același user (switch între ele)
  • Codurile expirate sunt auto-șterse
  • Mesajul cu parola e șters automat din chat
  • Rate limiting funcționează (max 3 emails/oră)
  • Error handling: mesaje clare pentru fiecare caz de eroare
  • Testare cu mmarius28@gmail.com funcționează end-to-end

📅 Next Steps După Implementare

Immediate (după deploy)

  1. Testare cu user real (Marius)

    • Verifică flow complet cu mmarius28@gmail.com
    • Test ambele metode de autentificare
    • Validează email delivery și formatting
  2. Monitoring

    • Watch logs pentru erori SMTP
    • Monitor rate limiting triggers
    • Check database growth (email_auth_codes table)

Short-term (1-2 săptămâni)

  1. Adaugă email pentru alți useri

    • Identifică 5-10 useri pentru beta testing
    • Adaugă email-uri în Oracle UTILIZATORI table
    • Anunță disponibilitatea noii metode
  2. Collect feedback

    • UX pentru conversation flow
    • Timing email delivery (sunt 5 min suficienți?)
    • Edge cases descoperite

Long-term (1-3 luni)

  1. Analytics

    • Câți useri folosesc fiecare metodă?
    • Success rate pentru fiecare flow
    • Common error patterns
  2. Optimizări

    • Migrate rate limiting la Redis (dacă scală)
    • Adaugă email templates multiple (română + engleză)
    • Consider migration de la SQLite la PostgreSQL (dacă >1000 users)
  3. Feature extensions

    • Email verification pentru schimbare parolă
    • Email notifications pentru events (reports ready, etc.)
    • Multi-language support

📞 Contact & Support

Developer: Claude Code (Anthropic) Project: ROA2WEB - ERP Reports Application Date: 2025-11-07

Pentru întrebări despre acest plan:

  • Review cod în reports-app/telegram-bot/ și reports-app/backend/
  • Consultă CLAUDE.md pentru context general
  • Check TELEGRAM_COMMANDS.md pentru comenzi existente

Resurse utile:


🎉 Conclusion

Acest plan implementează autentificare email + parolă (2FA) pentru Telegram Bot în mod minimal invaziv, păstrând întreaga funcționalitate existentă.

Key highlights:

  • 0 breaking changes - metoda veche funcționează în continuare
  • Simplu de implementat - ~13h efort total
  • Securizat - 2FA real cu rate limiting
  • Extensibil - ușor de scalat pentru mai mulți useri
  • User-friendly - conversation flow natural

Ready to implement! 🚀


Document generat: 2025-11-07 Versiune: 1.0 Status: Planning Phase - Ready for Implementation