# GitHub Integration

> OAuth authentication and repository access

## 📋 Overview

GitHub Integration provides OAuth-based authentication using Better Auth, enabling users to log in with their GitHub account and access their repositories for analysis and test generation.

## 🎯 Current Status

**Status**: ✅ Implemented

GitHub OAuth is fully functional for authentication. The GitHub App side (installation-scoped webhooks, Check Runs, sticky PR comments, and the PR-event delta orchestrator) is also live; see `apps/api/src/routes/githubApp.ts` and `packages/trpc/src/services/prDeltaOrchestrator.ts`.

## 🏗️ Authentication Architecture

### Better Auth Configuration

**Location**: `packages/auth/src/index.ts`

```typescript
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg'
  }),
  
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
      redirectURI: process.env.GITHUB_REDIRECT_URI || 
        `${process.env.BETTER_AUTH_URL}/auth/callback/github`
    }
  },
  
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24 // 1 day
  }
})
```

## 🔐 OAuth Flow

### 1. User Clicks "Sign in with GitHub"

```tsx
// Frontend button
<Button asChild>
  <a href={`${API_URL}/api/auth/github`}>
    <GithubIcon /> Sign in with GitHub
  </a>
</Button>
```

### 2. Redirect to GitHub

```
GET https://github.com/login/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://api.yourdomain.com/auth/callback/github
  &scope=read:user,user:email,repo
```

### 3. GitHub Callback

```typescript
// Better Auth handles this automatically
// Exchanges code for access token
// Creates/updates user in database
// Creates session
// Redirects to frontend
```

### 4. Frontend Receives Session

```tsx
// Frontend callback handler
export async function GitHubCallback() {
  const searchParams = useSearchParams()
  const session = searchParams.get('session')
  
  if (session) {
    // Store session
    localStorage.setItem('session', session)
    // Redirect to dashboard
    navigate('/dashboard')
  }
}
```

## 🔌 API Integration

### Database Schema

**accounts table** (Better Auth):

```typescript
{
  id: string
  userId: string
  providerId: 'github'
  accountId: string         // GitHub user ID
  accessToken: string       // OAuth access token
  refreshToken: string?
  accessTokenExpiresAt: Date?
  scope: string
  createdAt: Date
  updatedAt: Date
}
```

### Accessing GitHub API

```typescript
import { Octokit } from '@octokit/rest'

async function getUserRepositories(userId: string) {
  // Get GitHub access token from accounts table
  const account = await db.query.accounts.findFirst({
    where: and(
      eq(accounts.userId, userId),
      eq(accounts.providerId, 'github')
    )
  })
  
  if (!account?.accessToken) {
    throw new Error('GitHub not connected')
  }
  
  // Use Octokit with user's token
  const octokit = new Octokit({
    auth: account.accessToken
  })
  
  const { data } = await octokit.repos.listForAuthenticatedUser({
    sort: 'updated',
    per_page: 100
  })
  
  return data
}
```

## 🎨 Frontend Implementation

### GitHub Sign In Button

```tsx
import { GithubIcon } from 'lucide-react'
import { Button } from '~/components/ui/button'

export function GitHubSignIn() {
  const apiUrl = import.meta.env.VITE_API_URL

  return (
    <Button asChild variant="outline" className="w-full">
      <a href={`${apiUrl}/api/auth/github`}>
        <GithubIcon className="mr-2 h-4 w-4" />
        Sign in with GitHub
      </a>
    </Button>
  )
}
```

### Repository Selector

```tsx
export function RepositorySelector({ onSelect }: Props) {
  const { data, isLoading } = trpc.github.listRepositories.useQuery()

  return (
    <Command>
      <CommandInput placeholder="Search repositories..." />
      <CommandList>
        {isLoading && <CommandLoading />}
        
        <CommandGroup heading="Your Repositories">
          {data?.repositories.map((repo) => (
            <CommandItem
              key={repo.id}
              onSelect={() => onSelect(repo)}
            >
              <GithubIcon className="mr-2" />
              <div>
                <div>{repo.name}</div>
                <div className="text-sm text-muted-foreground">
                  {repo.fullName}
                </div>
              </div>
            </CommandItem>
          ))}
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
```

## 🪝 Webhooks (Planned)

### GitHub Webhook Setup

**Planned for Phase 4**:

```typescript
// Register webhook with GitHub
POST https://api.github.com/repos/owner/repo/hooks
{
  "config": {
    "url": "https://api.yourdomain.com/webhooks/github",
    "content_type": "json",
    "secret": "your_webhook_secret"
  },
  "events": ["push", "pull_request"],
  "active": true
}
```

### Webhook Handler (Planned)

```typescript
app.post('/webhooks/github', async (c) => {
  const signature = c.req.header('x-hub-signature-256')
  const payload = await c.req.json()
  
  // Verify signature
  if (!verifySignature(payload, signature)) {
    return c.json({ error: 'Invalid signature' }, 401)
  }
  
  // Handle event
  if (payload.action === 'push') {
    // Trigger analysis for new commit
    await analyzeRepository({
      repoUrl: payload.repository.clone_url,
      commitSha: payload.after
    })
  }
  
  return c.json({ received: true })
})
```

## 🔧 Configuration

### Environment Variables

```bash
# GitHub OAuth App
GITHUB_CLIENT_ID=Iv1.1234567890abcdef
GITHUB_CLIENT_SECRET=1234567890abcdef1234567890abcdef12345678
GITHUB_REDIRECT_URI=https://api.yourdomain.com/auth/callback/github

# Better Auth
BETTER_AUTH_SECRET=your_32_char_secret_here
BETTER_AUTH_URL=https://api.yourdomain.com
```

### GitHub OAuth App Setup

1. Go to GitHub Settings → Developer Settings → OAuth Apps
2. Click "New OAuth App"
3. Fill in details:
   - Application name: `Izri`
   - Homepage URL: `https://yourdomain.com`
   - Authorization callback URL: `https://api.yourdomain.com/auth/callback/github`
4. Copy Client ID and Client Secret
5. Add to environment variables

### Required Scopes

```
read:user      # Read user profile
user:email     # Read user email
repo           # Access repositories (for private repos)
```

## 🔐 Security

### Token Storage

- Access tokens stored encrypted in database
- Never exposed to frontend
- Used only on backend for API calls

### Token Refresh

Better Auth handles token refresh automatically when:

- Access token expires
- Refresh token is available

### Webhook Security

```typescript
import crypto from 'node:crypto'

function verifySignature(payload: any, signature: string): boolean {
  const secret = process.env.GITHUB_WEBHOOK_SECRET
  const hmac = crypto.createHmac('sha256', secret)
  const digest = 'sha256=' + hmac.update(JSON.stringify(payload)).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))
}
```

## ✅ Testing

### Mock GitHub OAuth

```typescript
describe('GitHub OAuth', () => {
  it('creates user after GitHub callback', async () => {
    const mockGitHubUser = {
      id: 123456,
      login: 'testuser',
      email: 'test@example.com'
    }
    
    // Simulate OAuth callback
    const user = await handleGitHubCallback(mockGitHubUser)
    
    expect(user.email).toBe('test@example.com')
    expect(user.accounts[0].providerId).toBe('github')
  })
})
```

## 🔗 Related Documentation

- **Authentication**: [Better Auth setup](../backend/authentication.md)
- **Auth Package**: [@izri/auth](../packages/auth-package.md)
- **Projects**: [Repository selection for projects](./projects.md)

---

*Next: [API Keys](./api-keys.md) →*
