Skip to content

Implement user identity-based authentication instead of hardcoded backend credentials #62

Description

@eshulman2

Description

Replace the current hardcoded backend credential system with proper user authentication. The backend should act on behalf of authenticated users using their identity/permissions, rather than requiring manual configuration of API keys and tokens in environment variables.

Current Issues

Backend Credential Management Problems

  1. Hardcoded Credentials: Backend requires manual configuration of Google credentials and Jira tokens in .env files
  2. Single Identity: All users share the same backend service account - no per-user permissions
  3. No Multi-tenancy: Can't support multiple users with different Google/Jira accounts
  4. Manual Setup: Each deployment requires manual credential configuration
  5. Security Risk: Backend credentials give broad access to all resources
  6. No User Context: Can't track which user performed which action

🚨 Claude Proposed Solution (Read/Implement with caution) 🚨

Current Flow (Single Backend Identity)

┌─────────────┐                    ┌──────────────────┐
│   Client    │  ─────────────────> │  Backend Server  │  Backend credentials (.env)
│  (Any User) │                    │  (Service Acct)  │  ────────────────────────────> Google/Jira
└─────────────┘                    └──────────────────┘  (Acts as single user)

Proposed Flow (User Identity-Based)

Option 1: User OAuth Flow

┌─────────────┐                    ┌──────────────────┐
│   Client    │  1. Login/Auth     │  Backend Server  │  User's OAuth token
│   (User A)  │  ─────────────────>│  (Authenticates  │  ────────────────────> Google/Jira
│             │  2. User token     │   User A)        │  (Acts as User A)
└─────────────┘  <─────────────────└──────────────────┘

Option 2: Service Account with Domain-Wide Delegation

┌─────────────┐                    ┌──────────────────┐
│   Client    │  User email        │  Backend Server  │  Impersonate user@domain.com
│   (user@    │  ─────────────────>│  (Service Acct   │  ─────────────────────────────> Google
│   domain)   │                    │   with DWD)      │  (Acts as user@domain.com)
└─────────────┘                    └──────────────────┘

Implementation

Option 1: User OAuth Flow (Recommended for Multi-User Deployments)

1. User Authentication Endpoints

# src/auth/oauth_handler.py

from fastapi import FastAPI, Depends, HTTPException
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow
import jwt

app = FastAPI()

class UserAuthManager:
    """Manage user OAuth authentication."""

    def __init__(self):
        self.sessions = {}  # In production, use Redis/database

    async def initiate_google_oauth(self, user_id: str):
        """Start Google OAuth flow for user."""
        flow = Flow.from_client_secrets_file(
            'client_secrets.json',
            scopes=[
                'https://www.googleapis.com/auth/calendar',
                'https://www.googleapis.com/auth/documents',
                'https://www.googleapis.com/auth/drive.readonly'
            ],
            redirect_uri='http://localhost:8000/auth/google/callback'
        )

        auth_url, state = flow.authorization_url(
            access_type='offline',
            include_granted_scopes='true'
        )

        self.sessions[state] = user_id
        return auth_url

    async def handle_oauth_callback(self, code: str, state: str):
        """Handle OAuth callback and store user credentials."""
        flow = Flow.from_client_secrets_file(
            'client_secrets.json',
            scopes=None,
            state=state,
            redirect_uri='http://localhost:8000/auth/google/callback'
        )

        flow.fetch_token(code=code)
        credentials = flow.credentials

        user_id = self.sessions.pop(state)

        # Store credentials securely (encrypt in production)
        await self.store_user_credentials(user_id, {
            'token': credentials.token,
            'refresh_token': credentials.refresh_token,
            'token_uri': credentials.token_uri,
            'client_id': credentials.client_id,
            'client_secret': credentials.client_secret,
            'scopes': credentials.scopes
        })

        return user_id

@app.get("/auth/google/login")
async def google_login(user_id: str):
    """Initiate Google OAuth login."""
    auth_url = await auth_manager.initiate_google_oauth(user_id)
    return {"auth_url": auth_url}

@app.get("/auth/google/callback")
async def google_callback(code: str, state: str):
    """Handle Google OAuth callback."""
    user_id = await auth_manager.handle_oauth_callback(code, state)
    return {"status": "authenticated", "user_id": user_id}

2. User-Context API Calls

# src/services/user_google_tools.py

class UserGoogleTools:
    """Google API calls using user's credentials."""

    async def get_user_credentials(self, user_id: str) -> Credentials:
        """Retrieve user's stored credentials."""
        creds_data = await credentials_store.get(user_id)

        credentials = Credentials(
            token=creds_data['token'],
            refresh_token=creds_data['refresh_token'],
            token_uri=creds_data['token_uri'],
            client_id=creds_data['client_id'],
            client_secret=creds_data['client_secret'],
            scopes=creds_data['scopes']
        )

        # Refresh if expired
        if credentials.expired and credentials.refresh_token:
            credentials.refresh(Request())
            await self.update_stored_credentials(user_id, credentials)

        return credentials

    async def create_calendar_event(
        self,
        user_id: str,
        title: str,
        description: str,
        start_time: str,
        duration_minutes: int,
        attendees: List[str]
    ):
        """Create calendar event using user's credentials."""
        credentials = await self.get_user_credentials(user_id)

        service = build('calendar', 'v3', credentials=credentials)

        # Create event on user's calendar
        event = service.events().insert(
            calendarId='primary',
            body={
                'summary': title,
                'description': description,
                # ... rest of event details
            }
        ).execute()

        return event['id']

Option 2: Service Account with Domain-Wide Delegation (Google Workspace)

1. Service Account Setup

# src/auth/service_account_delegated.py

from google.oauth2 import service_account
from googleapiclient.discovery import build

class DelegatedServiceAccount:
    """Service account with domain-wide delegation."""

    def __init__(self, service_account_file: str):
        self.service_account_file = service_account_file
        self.scopes = [
            'https://www.googleapis.com/auth/calendar',
            'https://www.googleapis.com/auth/documents',
            'https://www.googleapis.com/auth/drive.readonly'
        ]

    def get_delegated_credentials(self, user_email: str):
        """Get credentials impersonating a specific user."""
        credentials = service_account.Credentials.from_service_account_file(
            self.service_account_file,
            scopes=self.scopes,
            subject=user_email  # Impersonate this user
        )
        return credentials

    async def create_calendar_event_for_user(
        self,
        user_email: str,
        title: str,
        description: str,
        start_time: str,
        duration_minutes: int
    ):
        """Create calendar event as specific user."""
        credentials = self.get_delegated_credentials(user_email)
        service = build('calendar', 'v3', credentials=credentials)

        # Create event on user's calendar
        event = service.events().insert(
            calendarId='primary',
            body={
                'summary': title,
                'description': description,
                # ... event details
            }
        ).execute()

        return event['id']

2. Updated Workflow Integration

# src/core/workflow_servers/action_items_server.py

class ActionItemsRequest(BaseModel):
    user_email: str  # NEW: User's email for identity
    meeting_notes: str
    date: str

@app.post("/generate")
async def generate_action_items(request: ActionItemsRequest):
    """Generate action items for specific user."""

    # Use user's identity for all operations
    google_tools = DelegatedServiceAccount('service_account.json')

    # Read meeting notes from user's Drive
    notes_content = await google_tools.read_document_for_user(
        user_email=request.user_email,
        document_id=request.notes_doc_id
    )

    # Generate action items...
    # Dispatch to agents with user context...

3. CLI Client Changes

# src/clients/meeting_actions_client.py

class MeetingActionsClient:

    def run(self):
        # Get user's email (they must be in the Google Workspace domain)
        user_email = self.get_input("Your email", default=os.getenv("USER_EMAIL"))

        # All backend calls include user email
        response = requests.post(
            f"{self.url}/generate",
            json={
                "user_email": user_email,  # Backend impersonates this user
                "meeting_notes": notes,
                "date": date
            }
        )

Benefits

1. User-Based Permissions

  • ✅ Each user operates with their own Google/Jira permissions
  • ✅ Users can only access their own resources
  • ✅ No need for backend to have broad access to all resources
  • ✅ Respects existing Google Workspace/Jira permissions

2. Multi-Tenancy Support

  • ✅ Multiple users can use the same backend deployment
  • ✅ Each user has isolated access to their own data
  • ✅ No credential configuration needed per user
  • ✅ Scalable to organization-wide deployment

3. Security

  • ✅ No hardcoded credentials in .env files
  • ✅ Principle of least privilege - users have only their own permissions
  • ✅ Token refresh handled automatically
  • ✅ Audit trail shows which user performed which action

4. Simplified Deployment

  • ✅ No manual credential setup for each backend instance
  • ✅ OAuth flow guides users through authentication
  • ✅ Service account option for Google Workspace deployments
  • ✅ Single setup for entire organization

Implementation Comparison

Feature Option 1: User OAuth Option 2: Service Account DWD
Best For Multi-user SaaS, public deployment Google Workspace organizations
User Experience One-time OAuth login per user Just provide email address
Setup Complexity Medium (OAuth app setup) High (requires Workspace admin)
Google Workspace Not required Required
Jira Support Yes (user OAuth) Limited (needs equivalent)
Token Storage Backend stores per-user tokens Service account only
Permissions User's own permissions User's permissions (via delegation)

Recommended Approach

Start with Option 1 (User OAuth) because:

  • Works for both Google Workspace and personal Google accounts
  • Easier to set up initially
  • Better for Jira integration
  • Can add Option 2 later for Workspace-specific deployments

Acceptance Criteria

Option 1 (User OAuth) - Recommended

  • OAuth authentication flow implemented
  • User credential storage (encrypted)
  • Token refresh logic
  • Google OAuth endpoints (/auth/google/login, /auth/google/callback)
  • Jira OAuth endpoints (if needed)
  • User-context API calls for Google (calendar, docs, drive)
  • User-context API calls for Jira
  • CLI client updated to handle OAuth flow
  • Session/token management
  • Multi-user support in backend
  • User logout/credential revocation
  • Tests for authentication flow
  • Documentation with OAuth setup instructions

Option 2 (Service Account DWD) - Optional

  • Service account with domain-wide delegation configured
  • User impersonation logic
  • Backend accepts user_email parameter
  • All Google API calls use delegated credentials
  • CLI client includes user email in requests
  • Documentation for Workspace admin setup
  • Tests for delegation

Priority: 🔴 High
Effort: 12-16 hours (Option 1), 8-10 hours (Option 2 if Workspace-only)
Difficulty: High
Security Impact: High - Eliminates hardcoded credentials, enables multi-user support

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions