# Gmail OAuth + OTP Registration Implementation Plan

## Overview
Implement Gmail OAuth with OTP-based registration flow for jadwaai.com, allowing users to register using their Gmail account or personal email with OTP verification, and seamlessly use their Google account for NotebookLM creation.

## Current State

### Authentication System
- Traditional email/password only (`/app/routes/auth.py`)
- No social login or OAuth implementation
- Token-based email verification (24-hour expiry)
- User model has `email` field (unique, indexed)

### Google Integration
- NotebookLM integration exists via Playwright (`app/ai/notebooklm_service.py`)
- Uses `storage_state.json` for session management
- Separate manual login for NotebookLM (not integrated with auth)

### Registration Flow (Current)
```
/auth/register (GET)
├── Name
├── Email
├── Password
├── Account Type (entrepreneur, investor, factory, incubator)
├── Company Name (conditional)
├── Organization (autocomplete)
├── Interests (checkboxes)
└── Terms acceptance
```

---

## New Registration Flow

```
/auth/register (GET)
├── STEP 1: Profile Data
│   ├── Name
│   ├── Account Type
│   ├── Company Name (if not entrepreneur)
│   ├── Organization (if entrepreneur)
│   └── Interests
│
└── STEP 2: Email Verification
    ├── Option A: Gmail
    │   └── Google OAuth → Send OTP to Gmail
    │
    └── Option B: Personal Email
        └── Enter email → Send OTP to that email
        └── Verify OTP → Create Account
```

---

## Implementation Details

### Phase 1: Database Model Changes

**File**: `app/models/user.py`

Add new fields:

```python
# OAuth fields
google_id = db.Column(db.String(255), unique=True, nullable=True, index=True)
google_access_token = db.Column(db.Text, nullable=True)
google_refresh_token = db.Column(db.Text, nullable=True)
google_email = db.Column(db.String(255), nullable=True)
auth_provider = db.Column(db.String(20), default="email")  # "email" or "google"

# OTP fields
otp_code = db.Column(db.String(10), nullable=True, index=True)
otp_expires_at = db.Column(db.DateTime, nullable=True)

# Email preference
personal_email = db.Column(db.String(255), nullable=True)
email_preference = db.Column(db.String(20), default="primary")  # "primary", "gmail", "personal"
```

**Migration**: Create Alembic migration

---

### Phase 2: Google OAuth Setup

**New File**: `app/auth/google_oauth.py`

```python
"""
Google OAuth 2.0 client wrapper for authentication.
Handles Google login flow and token management.
"""
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow
from flask import session, url_for
import os

class GoogleOAuth:
    """Google OAuth client for user authentication."""

    def __init__(self):
        self.client_id = os.getenv("GOOGLE_CLIENT_ID")
        self.client_secret = os.getenv("GOOGLE_CLIENT_SECRET")
        self.redirect_uri = os.getenv(
            "GOOGLE_REDIRECT_URI",
            url_for("auth.google_callback", _external=True)
        )

    def get_authorization_url(self):
        """Generate Google OAuth authorization URL."""
        flow = Flow.from_client_config(
            client_config={
                "web": {
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                    "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
                    "token_uri": "https://oauth2.googleapis.com/token",
                }
            },
            scopes=[
                "openid",
                "email",
                "profile",
            ]
        )
        flow.redirect_uri = self.redirect_uri
        url, state = flow.authorization_url(
            access_type="offline",
            include_granted_scopes="true",
            prompt="consent"
        )
        session["oauth_state"] = state
        return url

    def fetch_token(self, authorization_response):
        """Exchange authorization code for tokens."""
        flow = Flow.from_client_config(
            client_config={
                "web": {
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                    "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
                    "token_uri": "https://oauth2.googleapis.com/token",
                }
            },
            scopes=["openid", "email", "profile"]
        )
        flow.redirect_uri = self.redirect_uri
        state = session.pop("oauth_state", None)
        if not state:
            raise ValueError("Invalid OAuth state")

        flow.fetch_token(authorization_response=authorization_response)
        credentials = flow.credentials

        return {
            "token": credentials.token,
            "refresh_token": credentials.refresh_token,
            "id_token": credentials.id_token,
            "client_id": credentials.client_id,
            "client_secret": credentials.client_secret,
        }

    def get_user_info(self, credentials_dict):
        """Get user info from ID token."""
        import json
        from google.auth.transport import requests
        from google.oauth2 import id_token

        try:
            id_info = id_token.verify_oauth2_token(
                credentials_dict["id_token"],
                requests.Request(),
                credentials_dict["client_id"]
            )
            return {
                "google_id": id_info["sub"],
                "email": id_info["email"],
                "name": id_info.get("name", ""),
                "verified_email": id_info.get("email_verified", False),
                "picture": id_info.get("picture", ""),
            }
        except Exception as e:
            raise ValueError(f"Invalid ID token: {e}")
```

---

**New File**: `app/auth/otp_service.py`

```python
"""
OTP generation, storage, and verification service.
"""
import random
import string
from datetime import datetime, timedelta
from flask import current_app, flash
from flask_mail import Message
from app.extensions import mail

def generate_otp(length: int = 6) -> str:
    """Generate random OTP code."""
    return "".join(random.choices(string.digits, k=length))

def send_otp_email(email: str, otp: str, user_name: str = ""):
    """Send OTP email to user."""
    msg = Message(
        "Jadwa AI - Your Verification Code",
        recipients=[email],
    )
    msg.html = f"""
    <div style="font-family:Arial,sans-serif;max-width:500px;margin:0 auto;padding:20px;text-align:center">
        <h2 style="color:#D2AF20">Jadwa AI</h2>
        <p style="font-size:16px;margin-bottom:20px">
            Hello {user_name or 'User'},<br>
            Your verification code is:
        </p>
        <div style="background:#D2AF20;color:#000;font-size:32px;font-weight:bold;
                    padding:20px 40px;border-radius:10px;letter-spacing:5px;margin:20px 0;">
            {otp}
        </div>
        <p style="color:#666;font-size:14px">
            This code expires in 10 minutes.<br>
            If you didn't request this, please ignore this email.
        </p>
    </div>
    """
    mail.send(msg)

def store_otp(user_id: int, otp: str, expiry_minutes: int = 10):
    """Store OTP for user."""
    from app.models.user import User
    user = User.query.get(user_id)
    if user:
        user.otp_code = otp
        user.otp_expires_at = datetime.utcnow() + timedelta(minutes=expiry_minutes)
        current_app.db.session.commit()

def verify_otp(user, code: str) -> bool:
    """Verify OTP for user."""
    if not user or not user.otp_code or not user.otp_expires_at:
        return False
    if datetime.utcnow() > user.otp_expires_at:
        return False
    return user.otp_code == code

def clear_otp(user):
    """Clear used OTP."""
    if user:
        user.otp_code = None
        user.otp_expires_at = None
        current_app.db.session.commit()
```

---

### Phase 3: Configuration Updates

**File**: `app/config.py`

```python
# Google OAuth Configuration
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "")
GOOGLE_REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI", "https://jadwaai.com/auth/google/callback")
```

**File**: `requirements.txt`

```
google-auth>=2.23.0
google-auth-oauthlib>=1.1.0
google-auth-httplib2>=0.2.0
google-api-python-client>=2.100.0
```

---

### Phase 4: New Routes

**File**: `app/routes/auth.py` (Additions)

```python
from app.auth.google_oauth import GoogleOAuth
from app.auth.otp_service import generate_otp, send_otp_email, store_otp, verify_otp, clear_otp

# ... existing routes ...

# ==================== NEW REGISTRATION FLOW ====================

@auth_bp.route("/register/step1", methods=["POST"])
@limiter.limit("10 per hour")
def register_step1():
    """Step 1: Collect profile data, store in session."""
    name = request.form.get("name", "").strip()
    account_type = request.form.get("account_type", "entrepreneur").strip()
    company_name = request.form.get("company_name", "").strip() or None
    organization_id = request.form.get("organization_id")
    interests = request.form.getlist("interests")

    if not name:
        flash_i18n("flash.name_required", "error")
        return redirect(url_for("auth.register"))

    # Store in session for step 2
    session["reg_data"] = {
        "name": name,
        "account_type": account_type,
        "company_name": company_name,
        "organization_id": organization_id,
        "interests": interests,
    }

    return redirect(url_for("auth.register_step2"))


@auth_bp.route("/register/step2", methods=["GET"])
def register_step2():
    """Step 2: Email verification with Gmail/Personal selection."""
    reg_data = session.get("reg_data", {})
    if not reg_data:
        return redirect(url_for("auth.register"))

    return render_template("auth/register_step2.html")


@auth_bp.route("/register/send-otp", methods=["POST"])
@limiter.limit("10 per hour")
def send_otp():
    """Send OTP to selected email (Gmail or Personal)."""
    email_type = request.form.get("email_type", "personal")
    email = request.form.get("email", "").strip().lower()

    if email_type == "gmail" and not email.endswith("@gmail.com"):
        flash("Please enter a Gmail address.", "error")
        return redirect(url_for("auth.register_step2"))

    # Check if email already exists
    if User.query.filter_by(email=email).first():
        flash("This email is already registered.", "error")
        return redirect(url_for("auth.register_step2"))

    # Generate and store OTP
    otp = generate_otp()
    # Store OTP in session (temporary, not in DB yet)
    session["reg_otp"] = {
        "code": otp,
        "email": email,
        "expires_at": (datetime.utcnow() + timedelta(minutes=10)).isoformat(),
    }

    # Send OTP email
    reg_data = session.get("reg_data", {})
    send_otp_email(email, otp, reg_data.get("name", ""))

    session["reg_email"] = email
    session["reg_email_type"] = email_type

    return redirect(url_for("auth.register_step3"))


@auth_bp.route("/register/step3", methods=["GET", "POST"])
@limiter.limit("10 per hour")
def register_step3():
    """Step 3: Verify OTP and create account."""
    reg_data = session.get("reg_data")
    reg_otp = session.get("reg_otp")

    if not reg_data or not reg_otp:
        return redirect(url_for("auth.register"))

    if request.method == "POST":
        user_otp = request.form.get("otp", "").strip()

        # Verify OTP
        expires_at = datetime.fromisoformat(reg_otp["expires_at"])
        if datetime.utcnow() > expires_at:
            flash("OTP has expired. Please request a new one.", "error")
            return redirect(url_for("auth.register_step2"))

        if user_otp != reg_otp["code"]:
            flash("Invalid OTP. Please try again.", "error")
            return render_template("auth/register_step3.html")

        # Create user account
        email = session.get("reg_email")
        email_type = session.get("reg_email_type")

        user = User(
            name=reg_data["name"],
            email=email,
            account_type=reg_data["account_type"],
            company_name=reg_data["company_name"],
            organization_id=reg_data["organization_id"],
            interests_json=reg_data["interests"] if reg_data["interests"] else None,
            email_verified=True,  # OTP verified
            email_verified_at=datetime.utcnow(),
            auth_provider="google" if email_type == "gmail" else "email",
        )

        # Handle organization selection
        if reg_data["organization_id"]:
            try:
                user.organization_id = int(reg_data["organization_id"])
            except (ValueError, TypeError):
                pass

        # Set random password (not used for Google auth)
        user.set_password(secrets.token_urlsafe(32))

        db.session.add(user)

        # Auto-create FactoryPartner for factory accounts
        if reg_data["account_type"] == "factory" and reg_data["company_name"]:
            from app.models.services import FactoryPartner
            fp = FactoryPartner(
                name=reg_data["company_name"], name_ar=reg_data["company_name"],
                user_id=user.id, sectors_json=reg_data["interests"],
                is_verified=False, is_active=True,
            )
            db.session.add(fp)
            user.factory_partner_id = fp.id

        db.session.commit()

        # Clear session
        session.pop("reg_data", None)
        session.pop("reg_otp", None)
        session.pop("reg_email", None)
        session.pop("reg_email_type", None)

        login_user(user)
        flash_i18n("flash.register_success", "success")
        return redirect(url_for("dashboard.index"))

    return render_template("auth/register_step3.html")


# ==================== GOOGLE OAUTH ROUTES ====================

@auth_bp.route("/google/login")
def google_login():
    """Start Google OAuth login flow."""
    oauth = GoogleOAuth()
    auth_url = oauth.get_authorization_url()
    return redirect(auth_url)


@auth_bp.route("/google/callback")
@limiter.limit("10 per hour")
def google_callback():
    """Handle Google OAuth callback."""
    oauth = GoogleOAuth()
    try:
        # Get authorization response
        auth_response = request.url

        # Fetch tokens
        credentials = oauth.fetch_token(auth_response)

        # Get user info
        user_info = oauth.get_user_info(credentials)

        # Check if user exists
        user = User.query.filter_by(
            google_id=user_info["google_id"]
        ).first()

        if user:
            # Existing user - login
            login_user(user)

            # Update tokens
            user.google_access_token = credentials["token"]
            user.google_refresh_token = credentials["refresh_token"]
            db.session.commit()

            flash("Logged in with Google successfully!", "success")
            return redirect(url_for("dashboard.index"))

        # New user - check if email already exists
        existing_user = User.query.filter_by(
            email=user_info["email"]
        ).first()

        if existing_user:
            # Link existing account
            existing_user.google_id = user_info["google_id"]
            existing_user.google_access_token = credentials["token"]
            existing_user.google_refresh_token = credentials["refresh_token"]
            existing_user.google_email = user_info["email"]
            existing_user.auth_provider = "google"
            existing_user.email_verified = True
            existing_user.email_verified_at = datetime.utcnow()

            if not existing_user.avatar_url and user_info.get("picture"):
                existing_user.avatar_url = user_info["picture"]

            db.session.commit()
            login_user(existing_user)
            flash("Your Google account has been linked!", "success")
            return redirect(url_for("dashboard.index"))

        # Create new user from Google
        user = User(
            name=user_info.get("name", "") or user_info["email"].split("@")[0],
            email=user_info["email"],
            google_id=user_info["google_id"],
            google_access_token = credentials["token"],
            google_refresh_token = credentials["refresh_token"],
            google_email=user_info["email"],
            auth_provider="google",
            email_verified=True,
            email_verified_at=datetime.utcnow(),
            language_pref="ar",
        )

        if user_info.get("picture"):
            user.avatar_url = user_info["picture"]

        user.set_password(secrets.token_urlsafe(32))
        db.session.add(user)
        db.session.commit()

        login_user(user)
        flash("Account created with Google!", "success")
        return redirect(url_for("dashboard.index"))

    except Exception as e:
        current_app.logger.error(f"Google OAuth error: {e}")
        flash("Failed to authenticate with Google. Please try again.", "error")
        return redirect(url_for("auth.login"))


# ==================== LOGIN ENHANCEMENTS ====================

@auth_bp.route("/login/google", methods=["GET"])
def login_with_google():
    """Redirect to Google OAuth for login."""
    return redirect(url_for("auth.google_login"))
```

---

### Phase 5: Template Updates

**New File**: `app/templates/auth/register_step2.html`

```html
{% extends "base.html" %}
{% block title %}Select Email Type{% endblock %}
{% block content %}
<div class="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4">
    <div class="max-w-md w-full bg-slate-900/70 backdrop-blur-xl p-10 rounded-3xl border border-slate-700">
        <div class="text-center mb-8">
            <h2 class="text-2xl font-serif font-bold text-white mb-2">Choose Your Email</h2>
            <p class="text-slate-400 text-sm">How would you like to verify your account?</p>
        </div>

        <div class="space-y-4">
            <!-- Gmail Option -->
            <form action="{{ url_for('auth.send_otp') }}" method="POST">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
                <input type="hidden" name="email_type" value="gmail">
                <div class="mb-4">
                    <input type="email" name="email" placeholder="your@gmail.com" required
                           class="w-full px-4 py-4 rounded-xl bg-black/50 border border-white/10 text-white focus:outline-none focus:ring-2 focus:ring-gold-500/50">
                </div>
                <button type="submit" class="w-full flex items-center justify-center gap-3 py-4 px-4 border border-slate-600 rounded-xl text-white bg-white/5 hover:bg-white/10 transition">
                    <svg class="w-5 h-5" viewBox="0 0 24 24">
                        <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
                        <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
                        <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
                        <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
                    </svg>
                    <span>Continue with Gmail</span>
                </button>
            </form>

            <div class="relative">
                <div class="absolute inset-0 flex items-center">
                    <div class="w-full border-t border-slate-700"></div>
                </div>
                <div class="relative flex justify-center text-sm">
                    <span class="px-2 bg-slate-900 text-slate-500">or</span>
                </div>
            </div>

            <!-- Personal Email Option -->
            <form action="{{ url_for('auth.send_otp') }}" method="POST">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
                <input type="hidden" name="email_type" value="personal">
                <div class="mb-4">
                    <input type="email" name="email" placeholder="your@email.com" required
                           class="w-full px-4 py-4 rounded-xl bg-black/50 border border-white/10 text-white focus:outline-none focus:ring-2 focus:ring-gold-500/50">
                </div>
                <button type="submit" class="w-full py-4 px-4 border border-transparent rounded-xl text-white bg-gradient-to-r from-gold-400 to-gold-600 hover:from-gold-300 hover:to-gold-500 transition shadow-[0_0_20px_rgba(210,175,32,0.3)]">
                    Send OTP to Personal Email
                </button>
            </form>

            <div class="text-center">
                <a href="{{ url_for('auth.register') }}" class="text-slate-500 text-sm hover:text-slate-400">← Back to Profile</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}
```

---

**New File**: `app/templates/auth/register_step3.html`

```html
{% extends "base.html" %}
{% block title %}Verify OTP{% endblock %}
{% block content %}
<div class="min-h-[calc(100vh-80px)] flex items-center justify-center py-12 px-4">
    <div class="max-w-md w-full bg-slate-900/70 backdrop-blur-xl p-10 rounded-3xl border border-slate-700">
        <div class="text-center mb-8">
            <div class="w-16 h-16 bg-gold-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
                <svg class="w-8 h-8 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                </svg>
            </div>
            <h2 class="text-2xl font-serif font-bold text-white mb-2">Verify Your Email</h2>
            <p class="text-slate-400 text-sm">Enter the 6-digit code sent to your email</p>
        </div>

        <form action="{{ url_for('auth.register_step3') }}" method="POST">
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

            <div class="mb-6">
                <div class="flex justify-center gap-2">
                    <input type="text" name="otp" maxlength="6" pattern="\d{6}" required
                           class="w-full max-w-xs px-4 py-4 text-center text-2xl font-bold tracking-widest rounded-xl bg-black/50 border border-white/10 text-white focus:outline-none focus:ring-2 focus:ring-gold-500/50"
                           placeholder="000000">
                </div>
            </div>

            <button type="submit" class="w-full py-4 px-4 border border-transparent rounded-xl text-white bg-gradient-to-r from-gold-400 to-gold-600 hover:from-gold-300 hover:to-gold-500 transition shadow-[0_0_20px_rgba(210,175,32,0.3)]">
                Verify & Create Account
            </button>
        </form>

        <div class="text-center mt-6">
            <a href="{{ url_for('auth.send_otp') }}" class="text-gold-400 text-sm hover:text-gold-300">Resend OTP</a>
        </div>
    </div>
</div>
{% endblock %}
```

---

**Modified File**: `app/templates/auth/login.html`

Add Google login button above the form:

```html
<!-- Add this before the existing form -->
<div class="mb-6">
    <a href="{{ url_for('auth.login_with_google') }}"
       class="w-full flex items-center justify-center gap-3 py-4 px-4 border border-slate-600 rounded-xl text-white bg-white/5 hover:bg-white/10 transition">
        <svg class="w-5 h-5" viewBox="0 0 24 24">
            <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
            <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
            <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
            <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
        </svg>
        <span>Continue with Google</span>
    </a>
</div>

<div class="relative my-6">
    <div class="absolute inset-0 flex items-center">
        <div class="w-full border-t border-slate-700"></div>
    </div>
    <div class="relative flex justify-center text-sm">
        <span class="px-2 bg-slate-900 text-slate-500">or</span>
    </div>
</div>
```

---

### Phase 6: NotebookLM Integration

**Modified File**: `app/ai/notebooklm_service.py`

Add Google OAuth token management:

```python
async def _get_client(user_id=None):
    """Get an authenticated NotebookLM client for a specific user."""
    from notebooklm import NotebookLMClient
    from app.models.user import User
    from google.oauth2.credentials import Credentials
    import google_auth_httplib2

    if user_id and user_id != "admin":
        user = User.query.get(user_id)
        if user and user.google_access_token:
            # Use user's OAuth tokens
            credentials = Credentials(
                token=user.google_access_token,
                refresh_token=user.google_refresh_token,
                token_uri="https://oauth2.googleapis.com/token",
                client_id=current_app.config.get("GOOGLE_CLIENT_ID"),
                client_secret=current_app.config.get("GOOGLE_CLIENT_SECRET"),
            )

            # Check if token needs refresh
            if credentials.expired:
                credentials.refresh(google_auth_httplib2.Http())
                # Update stored tokens
                user.google_access_token = credentials.token
                db.session.commit()

            # Set environment for notebooklm library
            os.environ["NOTEBOOKLM_HOME"] = get_user_session_dir(user_id)

            client = await NotebookLMClient.from_storage()
            return client
        else:
            raise Exception("User has not connected Google account. Please connect first.")

    return await NotebookLMClient.from_storage()
```

---

### Phase 7: Security Considerations

1. **CSRF Protection**: All forms include `csrf_token()`
2. **Rate Limiting**:
   - Registration: 10 per hour
   - OTP send: 10 per hour per IP
   - Google OAuth: 10 per hour per IP
3. **OTP Security**:
   - 6-digit numeric code
   - 10-minute expiry
   - Single use (cleared after verification)
4. **Token Storage**: OAuth tokens stored in database (consider encryption)
5. **Audit Logging**: All OAuth events logged to AuditLog table

---

### Phase 8: Google Cloud Console Setup

**Instructions**:

1. Go to https://console.cloud.google.com/
2. Create new project: "Jadwa AI Auth"
3. Navigate to **APIs & Services** → **Credentials**
4. Click **Create Credentials** → **OAuth 2.0 Client ID**
5. Configure:
   - Application type: **Web application**
   - Name: "Jadwa AI Auth"
   - Authorized redirect URIs:
     - `https://jadwaai.com/auth/google/callback`
     - `http://localhost:5000/auth/google/callback` (for development)
6. Add **Authorized domain**: `jadwaai.com`
7. Save and note the **Client ID** and **Client Secret**
8. Enable required APIs:
   - Google Identity Services API
   - OAuth 2.0 API

---

### Phase 9: Environment Variables

Add to `.env` file:

```env
# Google OAuth Configuration
GOOGLE_CLIENT_ID=your_actual_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_actual_client_secret
GOOGLE_REDIRECT_URI=https://jadwaai.com/auth/google/callback
```

---

### Phase 10: Database Migration

Create migration file:

```python
"""add_oauth_fields

Revision ID: xxx_add_oauth_fields
Revises:
Create Date: 2026-03-17

"""
from alembic import op
import sqlalchemy as sa


def upgrade():
    # Add OAuth and OTP fields to users table
    op.add_column('users', sa.Column('google_id', sa.String(255), nullable=True, index=True))
    op.add_column('users', sa.Column('google_access_token', sa.Text(), nullable=True))
    op.add_column('users', sa.Column('google_refresh_token', sa.Text(), nullable=True))
    op.add_column('users', sa.Column('google_email', sa.String(255), nullable=True))
    op.add_column('users', sa.Column('auth_provider', sa.String(20), nullable=True, server_default='email'))
    op.add_column('users', sa.Column('otp_code', sa.String(10), nullable=True, index=True))
    op.add_column('users', sa.Column('otp_expires_at', sa.DateTime(), nullable=True))
    op.add_column('users', sa.Column('personal_email', sa.String(255), nullable=True))
    op.add_column('users', sa.Column('email_preference', sa.String(20), nullable=True, server_default='primary'))

    # Create unique index for google_id
    op.create_index('ix_users_google_id_unique', 'users', ['google_id'], unique=True)


def downgrade():
    # Remove OAuth and OTP fields
    op.drop_index('ix_users_google_id_unique', table_name='users')
    op.drop_column('users', 'email_preference')
    op.drop_column('users', 'personal_email')
    op.drop_column('users', 'otp_expires_at')
    op.drop_column('users', 'otp_code')
    op.drop_column('users', 'auth_provider')
    op.drop_column('users', 'google_email')
    op.drop_column('users', 'google_refresh_token')
    op.drop_column('users', 'google_access_token')
    op.drop_column('users', 'google_id')
```

---

### Phase 11: Testing Checklist

- [ ] Profile data collection (Step 1)
- [ ] Gmail OTP registration flow
- [ ] Personal email OTP registration flow
- [ ] OTP generation and sending
- [ ] OTP verification
- [ ] OTP expiry handling (10 minutes)
- [ ] Resend OTP functionality
- [ ] Login with Google OAuth
- [ ] Login with email/password (existing)
- [ ] User creation from Google
- [ ] Linking Google to existing account
- [ ] NotebookLM creation with Google tokens
- [ ] Token refresh for expired sessions
- [ ] Rate limiting verification
- [ ] CSRF protection verification
- [ ] Error handling for OAuth failures
- [ ] Security audit

---

### Phase 12: Deployment Steps

1. **Setup Google Cloud Console**:
   - Create OAuth credentials
   - Configure redirect URIs
   - Enable APIs

2. **Update Environment**:
   - Add Google OAuth credentials to `.env`

3. **Install Dependencies**:
   ```bash
   pip install -r requirements.txt
   ```

4. **Run Database Migration**:
   ```bash
   flask db upgrade
   ```

5. **Test on Staging**:
   - Verify all registration flows
   - Test OTP sending
   - Test Google OAuth

6. **Deploy to Production**:
   - Update production `.env`
   - Run migration
   - Monitor logs

---

### Rollback Plan

If issues occur:

1. Revert database migration: `flask db downgrade`
2. Remove Google OAuth config from `.env`
3. Existing email/password flow remains functional
4. No data loss (new fields are nullable)

---

## Summary

This plan implements a complete Gmail OAuth + OTP registration system that:

1. Allows users to register with Gmail or personal email
2. Sends OTP for verification
3. Uses Google OAuth for authentication
4. Integrates seamlessly with NotebookLM using stored tokens
5. Maintains backward compatibility with existing email/password flow
6. Follows security best practices (CSRF, rate limiting, OTP expiry)

Total files to create: 5
Total files to modify: 7
Estimated development time: 2-3 days
