# Fix: Vercel Deployment Protection Blocking Internal API Calls

**Date:** October 18, 2025
**Status:** ✅ FIXED
**Branch:** `main`
**Commit:** `a0cc8120`

---

## 🔴 Problem Summary

After fixing the server-side URL construction issue, scheduled backups were still failing with a **401 Authentication Required** error. The error showed an HTML authentication page from Vercel instead of the expected JSON response.

### Error Details

```
Last error: Execute endpoint failed: 401 <!doctype html><html lang=en>...
<title>Authentication Required</title>
```

This error occurred when the cron endpoint tried to call the execute endpoint via HTTP fetch.

---

## 🔍 Root Cause Analysis

### The Issue

**Vercel Deployment Protection** was blocking internal server-to-server HTTP calls.

**How Vercel Deployment Protection Works:**
1. Vercel can enable deployment protection on deployments (Preview/Production)
2. This protection requires authentication to access ANY URL on the deployment
3. The authentication happens **before** Next.js middleware runs
4. Even internal server-to-server fetch() calls are blocked

**Why This Happened:**
- The cron endpoint was making an HTTP `fetch()` call to the execute endpoint.
- Even though both endpoints are in the same application.
- Even though the middleware marks these routes as public.
- Vercel's deployment protection intercepts the request before it reaches Next.js.
- Returns a 401 HTML authentication page instead of allowing the request through.

### Why Middleware Couldn't Help

```typescript
// src/middleware.ts
const isPublicRoute = createRouteMatcher([
  "/api/cron(.*)",
  "/api/jobs/(.+)/execute",
  // ... other routes
]);
```

This middleware configuration is correct, but it doesn't matter because:
- Vercel deployment protection runs **before** Next.js middleware.
- The request never reaches the Next.js application.
- Therefore, the middleware never has a chance to allow the request.

---

## ✅ Solution

### Architecture Change: Direct Function Calls Instead of HTTP Requests

Instead of making HTTP fetch() calls between server-side endpoints, we now call the business logic functions directly.

**Before (Broken):**
```
Cron Endpoint → HTTP fetch() → Execute Endpoint → HTTP fetch() → Batch Copy Endpoint
                     ↓ 401                              ↓ 401
              Vercel Protection              Vercel Protection
```

**After (Fixed):**
```
Cron Endpoint → executeBackupJob() → executeBatchCopyOperations()
                (direct function call)   (direct function call)
```

### Implementation Details

**1. Created Shared Backup Execution Logic**

**File:** `src/lib/backup/execute-backup-job.ts`

```typescript
export async function executeBackupJob(
  jobId: string,
  userId: string,
): Promise<ExecuteBackupJobResult> {
  // All backup execution logic moved here
  // Can be called directly without HTTP requests
}
```

**Benefits:**
- No HTTP requests = No deployment protection issues.
- Reusable logic for both cron and manual execution.
- Easier to test (no need to mock HTTP calls).
- Better performance (no HTTP overhead).

**2. Updated Cron Endpoint**

**File:** `src/app/api/cron/execute-backups/route.ts`

**Before:**
```typescript
const executeUrl = await getServerApiUrl(`/api/jobs/${job.id}/execute`);
const executeRes = await fetch(executeUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${cronSecret}`,
  },
});
```

**After:**
```typescript
const result = await executeBackupJob(job.id, job.userId);
```

**3. Updated Execute API Route**

**File:** `src/app/api/jobs/[id]/execute/route.ts`

The API route is now a thin wrapper around `executeBackupJob()`:

```typescript
export async function POST(req: NextRequest, { params }) {
  const { id: jobId } = await params;

  // Get userId from auth or job
  const userId = await getUserId(req, jobId);

  // Execute using shared logic
  const result = await executeBackupJob(jobId, userId);

  return createSuccessResponse(result);
}
```

**4. Enhanced Copy Service**

**File:** `src/lib/rclone/services/copy-service.ts`

Added optional `userId` parameter to support server-side token retrieval:

```typescript
export async function executeBatchCopyOperations(
  operations: CopyOperationRequest[],
  userId?: string, // For server-side token retrieval
): Promise<...> {
  // Pass userId to each operation for database token access
}
```

---

## 📊 Technical Benefits

### 1. **No More HTTP Overhead**
- Direct function calls are faster than HTTP requests.
- No serialization/deserialization of JSON.
- No network latency (even for localhost).

### 2. **Better Error Handling**
- Errors are thrown directly, not wrapped in HTTP responses.
- Stack traces are preserved.
- Easier to debug.

### 3. **Simpler Code**
- No need to construct URLs.
- No need to handle HTTP status codes.
- No need to parse response bodies.

### 4. **More Testable**
- Can test business logic without mocking HTTP.
- Can test with different user contexts easily.
- Can test error scenarios more easily.

### 5. **Deployment Protection Proof**
- Works regardless of Vercel deployment protection settings.
- Works in all environments (Development, Preview, Production).
- No authentication bypass tokens needed.

---

## 🧪 Testing & Verification

### Before Deploying

1. **Type Check:**
   ```bash
   pnpm typecheck
   ```
   Result: ✅ No errors

2. **Lint Check:**
   ```bash
   pnpm lint
   ```
   Result: ✅ No errors

### After Deploying to Production

1. **Wait for next cron execution** (within 5 minutes)

2. **Check backup job status:**
   ```bash
   node scripts/investigate-failed-backup.mjs
   ```
   Should show no new failures

3. **Verify in database:**
   - Jobs should be marked as "completed" instead of "failed".
   - No "401" errors in lastError field.

4. **Check destination folders:**
   - Navigate to backup destination in Google Drive/OneDrive.
   - Verify timestamped folders contain files (not empty).

5. **Monitor logs:**
   ```bash
   vercel logs --prod
   ```
   Look for successful backup execution logs

---

## 📝 Lessons Learned

### 1. **Vercel Deployment Protection is Aggressive**
- It blocks ALL requests, even internal ones.
- It runs before Next.js middleware.
- Cannot be bypassed with middleware configuration.

### 2. **Server-to-Server Communication Patterns**
- **Bad:** Making HTTP fetch() calls between endpoints in the same app.
- **Good:** Calling shared business logic functions directly.
- **Best:** Separating business logic from API route handlers.

### 3. **Architecture Best Practices**
- Keep business logic separate from HTTP handlers.
- Make logic reusable across different contexts (cron, API, CLI).
- Avoid HTTP calls when direct function calls are possible.

### 4. **Next.js Server-Side Patterns**
- Server-side code can import and call other server-side code directly.
- No need for HTTP when both endpoints are in the same application.
- HTTP should only be used for external services or client-server communication.

---

## 🔗 Related Issues & Documentation

### Previous Fixes
- [Server-Side URL Construction Fix](./BACKUP_EMPTY_DIRECTORIES_FIX_2025-10-25.md) - Fixed `NEXT_PUBLIC_SITE_URL` issue.
- [Cron Job Deployment Guide](../CRON_JOB_DEPLOYMENT.md) - Current and legacy scheduler context.

### Vercel Documentation
- [Deployment Protection](https://vercel.com/docs/security/deployment-protection).
- [Protection Bypass Methods](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection).
- [Environment Variables](https://vercel.com/docs/projects/environment-variables).

### Next.js Documentation
- [API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers).
- [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).

---

## ✅ Resolution Status

**Status:** FIXED
**Commit:** `a0cc8120`
**Files Changed:** 5 files, 556 insertions(+), 195 deletions(-)
**Ready for Deployment:** YES

**Changes:**
- ✅ Created `src/lib/backup/execute-backup-job.ts` - Shared backup execution logic.
- ✅ Updated `src/app/api/cron/execute-backups/route.ts` - Direct function calls.
- ✅ Updated `src/app/api/jobs/[id]/execute/route.ts` - Thin wrapper around shared logic.
- ✅ Updated `src/lib/rclone/services/copy-service.ts` - Added userId parameter.
- ✅ Created `BACKUP_EMPTY_DIRECTORIES_FIX.md` - Documentation for previous fix.

**Next Steps:**
1. Deploy to production with `vercel --prod` or merge to main branch
2. Monitor first cron execution (within 5 minutes)
3. Verify backup files are copied successfully
4. Confirm no more 401 authentication errors
5. Close related issues/tickets

---

## 🎯 Summary

The issue was caused by Vercel Deployment Protection blocking internal HTTP requests between API endpoints. The solution was to refactor the code to use direct function calls instead of HTTP fetch() calls. This is a better architecture anyway, as it's faster, more testable, and more maintainable.

**Key Takeaway:** When building server-side applications, prefer direct function calls over HTTP requests for internal communication. HTTP should be reserved for external services or client-server communication.

