feat: add authentication and configuration for sync operations

- Implemented SyncAuth to control sync access with server-only mode and secret key authorization
- Consolidated environment variables into single .env.example with improved sync configuration
- Protected all sync API endpoints (ubuntu, fedora, arch, homebrew, winget) with authentication checks
This commit is contained in:
Yusuf İpek
2025-11-11 16:41:37 +03:00
parent 6d39c9b48e
commit b37373e807
12 changed files with 904 additions and 19 deletions
+11 -2
View File
@@ -12,6 +12,15 @@ NEXTAUTH_SECRET=your_secret_here
# API Configuration # API Configuration
API_BASE_URL=http://localhost:3000/api API_BASE_URL=http://localhost:3000/api
# Cryptomus API Configuration
# Get these from your Cryptomus merchant dashboard: https://cryptomus.com/merchant
CRYPTOMUS_MERCHANT_ID=your_merchant_id_here
CRYPTOMUS_PAYMENT_API_KEY=your_payment_api_key_here
# Sync Configuration # Sync Configuration
SYNC_ENABLED=true # Enable sync operations only from the server itself (set to 'true' on production server)
SYNC_INTERVAL=3600000 # 1 hour in milliseconds SYNC_SERVER_ONLY=false
# Automatic sync frequency in days (set to 0 to disable automatic sync)
AUTO_SYNC_DAYS=1
# Secret key to authorize sync operations from server
SYNC_SECRET_KEY=your_sync_secret_key_here
-7
View File
@@ -1,7 +0,0 @@
# Cryptomus API Configuration
# Get these from your Cryptomus merchant dashboard: https://cryptomus.com/merchant
CRYPTOMUS_MERCHANT_ID=your_merchant_id_here
CRYPTOMUS_PAYMENT_API_KEY=your_payment_api_key_here
# NextAuth URL Configuration
NEXTAUTH_URL=http://localhost:3002
+211
View File
@@ -0,0 +1,211 @@
# RepoHub Sync Security Configuration
Bu doküman, RepoHub sync işlemlerinin güvenliği ve otomatikleştirilmesi için yapılandırmayı açıklar.
## Güvenlik Yapılandırması
### Environment Değişkenleri
`.env.local` dosyasına aşağı değişkenleri ekleyin:
```bash
# Sync Configuration
# Enable sync operations only from the server itself (set to 'true' on production server)
SYNC_SERVER_ONLY=true
# Automatic sync frequency in days (set to 0 to disable automatic sync)
AUTO_SYNC_DAYS=1
# Secret key to authorize sync operations from server
SYNC_SECRET_KEY=your_very_secure_secret_key_here
```
### Değişkenlerin Açıklaması
- **SYNC_SERVER_ONLY**: `true` olarak ayarlandığında, sync işlemleri sadece sunucudan yapılabilir
- **AUTO_SYNC_DAYS**: Otomatik sync sıklığı (gün olarak). `0` = otomatik sync kapalı
- **SYNC_SECRET_KEY**: Sunucu sync işlemleri için gerekli gizli anahtar
## Sunucu Sync İşlemleri
### 1. Manuel Sync (Sunucudan)
Sunucuda sync işlemleri yapmak için:
```bash
# Tüm platformları sync et
./scripts/server-sync.sh auto-sync
# Sadece Windows paketlerini sync et
./scripts/server-sync.sh sync-winget
# Sync durumunu kontrol et
./scripts/server-sync.sh status
```
### 2. Cron Job ile Otomatik Sync
Otomatik sync kurulumu için:
```bash
# Günlük otomatik sync kur
sudo ./scripts/cron-setup.sh install
# Haftalık otomatik sync kur (her 7 günde bir)
sudo AUTO_SYNC_DAYS=7 ./scripts/cron-setup.sh install
# Cron job durumunu kontrol et
sudo ./scripts/cron-setup.sh status
# Cron job'ı kaldır
sudo ./scripts/cron-setup.sh remove
```
## API Güvenliği
### Sync Endpoint'leri
Tüm sync endpoint'leri artık güvenlik kontrolü yapar:
- `/api/sync` - Genel sync endpoint'i
- `/api/sync-winget` - Windows paket sync'i
- `/api/sync-homebrew` - macOS paket sync'i
- `/api/sync-fedora` - Fedora paket sync'i
- `/api/sync-arch` - Arch paket sync'i
- `/api/auto-sync` - Otomatik sync
### Güvenlik Kontrolü
**Server-only modda (`SYNC_SERVER_ONLY=true`):**
1. **Secret Key Kontrolü**: Request header'ında `x-sync-secret` olmalı
2. **IP Kontrolü**: İstek localhost'tan gelmeli veya doğru secret key içermeli
**Normal modda (`SYNC_SERVER_ONLY=false`):**
- Herkes sync işlemi yapabilir (geliştirme için)
### Örnek API Kullanımı
```bash
# Server-only modda sync yapmak
curl -X POST \
-H "Content-Type: application/json" \
-H "x-sync-secret: your_secret_key" \
-d '{}' \
http://localhost:3002/api/sync-winget
# Sync durumunu kontrol et
curl -H "x-sync-secret: your_secret_key" \
http://localhost:3002/api/sync
```
## Güvenlik İpuçları
### 1. Secret Key Güvenliği
- Güçlü ve rastgele bir secret key kullanın
- Secret key'i `.env.local` dosyasında saklayın, asla kod içine koymayın
- Secret key'i düzenli olarak değiştirin
### 2. Sunucu Güvenliği
- Sync script'lerini sadece sunucuda çalıştırın
- Cron job'ları root kullanıcısı olarak ayarlayın
- Log dosyalarını düzenli olarak kontrol edin
### 3. Ağ Güvenliği
- Sync endpoint'lerini firewall ile koruyun
- Sadece localhost'tan erişime izin verin
- SSL/TLS kullanın (production'da)
## Monitoring ve Logging
### Log Dosyaları
- **Sync Log**: `/var/log/repohub-sync.log`
- **Cron Log**: `/var/log/cron.log` (sistem bağımlı)
### Log İzleme
```bash
# Son 20 sync log satırını göster
tail -20 /var/log/repohub-sync.log
# Real-time log izleme
tail -f /var/log/repohub-sync.log
# Cron job loglarını kontrol et
sudo tail -20 /var/log/cron.log
```
## Troubleshooting
### Yaygın Sorunlar
1. **403 Forbidden Error**
- `SYNC_SECRET_KEY` doğru ayarlanmamış
- Header'da `x-sync-secret` eksik
2. **Cron Job Çalışmıyor**
- Script executable değil
- Environment değişkenleri eksik
- Log dosyası izinleri yanlış
3. **Sync Başarısız**
- API URL yanlış
- Ağ bağlantısı sorunu
- Disk alanı yetersiz
### Debug Komutları
```bash
# Sync script test et
./scripts/server-sync.sh test
# Cron job durumunu kontrol et
sudo ./scripts/cron-setup.sh status
# Environment değişkenlerini kontrol et
env | grep SYNC
```
## Production Dağıtımı
### Adım 1: Environment Ayarı
```bash
# .env.local dosyasını production'a kopyala
cp .env.local.example .env.local
# .env.local dosyasını production değerleriyle düzenle
```
### Adım 2: Secret Key Oluştur
```bash
# Güçlü secret key oluştur
openssl rand -hex 32
# Bu değeri .env.local dosyasına ekle
```
### Adım 3: Cron Job Kur
```bash
# Production cron job kur
sudo AUTO_SYNC_DAYS=1 ./scripts/cron-setup.sh install
```
### Adım 4: Test Et
```bash
# Sync işlemini test et
./scripts/server-sync.sh test
# Otomatik sync'i test et
curl -X POST \
-H "Content-Type: application/json" \
-H "x-sync-secret: your_secret_key" \
-d '{}' \
http://localhost:3002/api/auto-sync
```
Bu yapılandırma ile sync işlemleriniz güvenli ve otomatik hale gelecektir.
+199
View File
@@ -0,0 +1,199 @@
#!/bin/bash
# Cron job setup script for RepoHub auto-sync
# This script sets up automatic sync using cron jobs
set -e
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SYNC_SCRIPT="$SCRIPT_DIR/server-sync.sh"
CRON_FILE="/etc/cron.d/repohub-sync"
LOG_FILE="/var/log/repohub-sync.log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if running as root
check_root() {
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root to setup cron jobs"
echo "Please run: sudo $0"
exit 1
fi
}
# Check if sync script exists
check_sync_script() {
if [ ! -f "$SYNC_SCRIPT" ]; then
log_error "Sync script not found: $SYNC_SCRIPT"
exit 1
fi
# Make sure sync script is executable
chmod +x "$SYNC_SCRIPT"
log_info "Sync script is ready: $SYNC_SCRIPT"
}
# Get sync frequency from environment or default to daily
get_sync_frequency() {
local frequency="${AUTO_SYNC_DAYS:-1}"
echo "$frequency"
}
# Setup cron job
setup_cron() {
local frequency=$(get_sync_frequency)
local cron_schedule="0 2 */$frequency * *" # Run at 2 AM every N days
log_info "Setting up cron job to run every $frequency day(s)"
log_info "Cron schedule: $cron_schedule"
# Create cron file
cat > "$CRON_FILE" << EOF
# RepoHub Auto Sync - Runs every $frequency day(s) at 2 AM
# Environment variables
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
API_URL=${API_URL:-http://localhost:3002}
SYNC_SECRET_KEY=$SYNC_SECRET_KEY
AUTO_SYNC_DAYS=$frequency
# Cron job
$cron_schedule root $SYNC_SCRIPT auto-sync >> $LOG_FILE 2>&1
# Manual sync status check (every hour)
0 * * * * root $SYNC_SCRIPT status >> $LOG_FILE 2>&1
EOF
# Set proper permissions
chmod 644 "$CRON_FILE"
# Reload cron service
if command -v systemctl >/dev/null 2>&1; then
systemctl reload cron || systemctl reload crond || true
elif command -v service >/dev/null 2>&1; then
service cron reload || service crond reload || true
fi
log_info "Cron job installed successfully: $CRON_FILE"
}
# Remove cron job
remove_cron() {
if [ -f "$CRON_FILE" ]; then
rm -f "$CRON_FILE"
log_info "Cron job removed: $CRON_FILE"
# Reload cron service
if command -v systemctl >/dev/null 2>&1; then
systemctl reload cron || systemctl reload crond || true
elif command -v service >/dev/null 2>&1; then
service cron reload || service crond reload || true
fi
else
log_warn "No cron job found to remove"
fi
}
# Show cron status
show_status() {
if [ -f "$CRON_FILE" ]; then
log_info "Cron job is installed:"
cat "$CRON_FILE"
else
log_warn "No cron job found"
fi
if [ -f "$LOG_FILE" ]; then
log_info "Recent sync logs:"
tail -20 "$LOG_FILE"
else
log_warn "No log file found: $LOG_FILE"
fi
}
# Test sync script
test_sync() {
log_info "Testing sync script..."
if [ -z "$SYNC_SECRET_KEY" ]; then
log_error "SYNC_SECRET_KEY environment variable is required for testing"
exit 1
fi
# Run a status check to test connectivity
"$SYNC_SCRIPT" status
}
# Show usage
show_usage() {
echo "Usage: $0 [COMMAND]"
echo ""
echo "Commands:"
echo " install Install cron job for auto-sync"
echo " remove Remove cron job"
echo " status Show cron job status and recent logs"
echo " test Test sync script connectivity"
echo " help Show this help message"
echo ""
echo "Environment variables:"
echo " API_URL API base URL (default: http://localhost:3002)"
echo " SYNC_SECRET_KEY Secret key for sync authorization"
echo " AUTO_SYNC_DAYS Sync frequency in days (default: 1)"
echo " LOG_FILE Log file path (default: /var/log/repohub-sync.log)"
echo ""
echo "Examples:"
echo " $0 install # Install daily cron job"
echo " AUTO_SYNC_DAYS=7 $0 install # Install weekly cron job"
echo " $0 status # Check cron job status"
echo " $0 test # Test sync script"
}
# Main script logic
main() {
case "${1:-}" in
"install")
check_root
check_sync_script
setup_cron
;;
"remove")
check_root
remove_cron
;;
"status")
show_status
;;
"test")
check_sync_script
test_sync
;;
"help"|"-h"|"--help")
show_usage
;;
*)
log_error "Unknown command: ${1:-}"
show_usage
exit 1
;;
esac
}
# Run main function
main "$@"
+185
View File
@@ -0,0 +1,185 @@
#!/bin/bash
# Server-side sync script for RepoHub
# This script can only be run from the server itself when SYNC_SERVER_ONLY=true
set -e
# Configuration
API_URL="${API_URL:-http://localhost:3002}"
SYNC_SECRET="${SYNC_SECRET_KEY}"
LOG_FILE="/var/log/repohub-sync.log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log_info() {
echo -e "${GREEN}$(date '+%Y-%m-%d %H:%M:%S') - INFO: $1${NC}" | tee -a "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}$(date '+%Y-%m-%d %H:%M:%S') - WARN: $1${NC}" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}$(date '+%Y-%m-%d %H:%M:%S') - ERROR: $1${NC}" | tee -a "$LOG_FILE"
}
# Check if required environment variables are set
check_env() {
if [ -z "$SYNC_SECRET" ]; then
log_error "SYNC_SECRET_KEY environment variable is required"
exit 1
fi
log_info "Environment variables validated"
}
# Perform sync for a specific platform
sync_platform() {
local platform="$1"
log_info "Starting sync for platform: $platform"
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/json" \
-H "x-sync-secret: $SYNC_SECRET" \
-d '{}' \
"$API_URL/api/sync-$platform")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [ "$http_code" -eq 200 ]; then
log_info "Successfully synced $platform: $body"
return 0
else
log_error "Failed to sync $platform (HTTP $http_code): $body"
return 1
fi
}
# Perform auto sync
auto_sync() {
log_info "Starting automatic sync for all platforms"
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/json" \
-H "x-sync-secret: $SYNC_SECRET" \
-d '{}' \
"$API_URL/api/auto-sync")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [ "$http_code" -eq 200 ]; then
log_info "Auto sync completed successfully: $body"
return 0
else
log_error "Auto sync failed (HTTP $http_code): $body"
return 1
fi
}
# Get sync status
get_status() {
log_info "Getting sync status"
response=$(curl -s -w "\n%{http_code}" -X GET \
-H "x-sync-secret: $SYNC_SECRET" \
"$API_URL/api/sync")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
if [ "$http_code" -eq 200 ]; then
log_info "Sync status: $body"
return 0
else
log_error "Failed to get sync status (HTTP $http_code): $body"
return 1
fi
}
# Show usage
show_usage() {
echo "Usage: $0 [COMMAND] [OPTIONS]"
echo ""
echo "Commands:"
echo " auto-sync Perform automatic sync for all platforms"
echo " sync-all Sync all platforms individually"
echo " sync-winget Sync Windows packages"
echo " sync-homebrew Sync macOS packages"
echo " sync-fedora Sync Fedora packages"
echo " sync-arch Sync Arch Linux packages"
echo " status Get current sync status"
echo ""
echo "Environment variables:"
echo " API_URL API base URL (default: http://localhost:3002)"
echo " SYNC_SECRET_KEY Secret key for sync authorization"
echo " LOG_FILE Log file path (default: /var/log/repohub-sync.log)"
echo ""
echo "Examples:"
echo " $0 auto-sync # Auto sync all platforms"
echo " $0 sync-winget # Sync only Windows packages"
echo " API_URL=https://api.repohub.com $0 status # Check status on production"
}
# Main script logic
main() {
# Create log file if it doesn't exist
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
log_info "RepoHub Server Sync Script started"
check_env
case "${1:-}" in
"auto-sync")
auto_sync
;;
"sync-all")
log_info "Syncing all platforms individually"
sync_platform "winget"
sync_platform "homebrew"
sync_platform "fedora"
sync_platform "arch"
;;
"sync-winget")
sync_platform "winget"
;;
"sync-homebrew")
sync_platform "homebrew"
;;
"sync-fedora")
sync_platform "fedora"
;;
"sync-arch")
sync_platform "arch"
;;
"status")
get_status
;;
"help"|"-h"|"--help")
show_usage
;;
*)
log_error "Unknown command: ${1:-}"
show_usage
exit 1
;;
esac
log_info "RepoHub Server Sync Script completed"
}
# Run main function with all arguments
main "$@"
+148
View File
@@ -0,0 +1,148 @@
import { NextRequest, NextResponse } from 'next/server'
import { SyncAuth } from '@/lib/sync/auth'
import { MetadataFetcher } from '@/services/metadataFetcher'
import { PackageFetcherV2 } from '@/services/packageFetcherV2'
import { WingetPackageFetcher } from '@/services/wingetPackageFetcher'
import { HomebrewPackageFetcher } from '@/services/homebrewPackageFetcher'
import { FedoraPackageFetcher } from '@/services/fedoraPackageFetcher'
import { ArchPackageFetcher } from '@/services/archPackageFetcher'
export const dynamic = 'force-dynamic'
export const maxDuration = 1800 // 30 minutes timeout for auto sync
// Simple in-memory store for last sync time (in production, use database)
let lastAutoSync: Date | null = null
export async function POST(request: NextRequest) {
try {
// Check if auto sync is enabled
if (!SyncAuth.isAutoSyncEnabled()) {
return NextResponse.json({
message: 'Auto sync is disabled',
next_sync: null
})
}
// Check if enough time has passed since last sync
const now = new Date()
const nextSyncTime = SyncAuth.getNextSyncTime(lastAutoSync || undefined)
if (now < nextSyncTime) {
return NextResponse.json({
message: 'Auto sync not due yet',
last_sync: lastAutoSync?.toISOString(),
next_sync: nextSyncTime.toISOString(),
hours_until_next: Math.ceil((nextSyncTime.getTime() - now.getTime()) / (1000 * 60 * 60))
})
}
console.log('🔄 Starting automatic package sync...')
// Sync all platforms in sequence
const syncResults = []
try {
// Sync Debian/Ubuntu packages
console.log('Syncing Debian/Ubuntu packages...')
await PackageFetcherV2.syncAll()
syncResults.push({ platform: 'debian/ubuntu', status: 'success' })
} catch (error) {
console.error('Debian/Ubuntu sync failed:', error)
syncResults.push({ platform: 'debian/ubuntu', status: 'failed', error: error instanceof Error ? error.message : 'Unknown error' })
}
try {
// Sync Windows packages (Winget)
console.log('Syncing Windows packages...')
const wingetFetcher = new WingetPackageFetcher()
const wingetPackages = await wingetFetcher.fetchAllPackages()
await wingetFetcher.storePackages(wingetPackages)
syncResults.push({ platform: 'windows', status: 'success', package_count: wingetPackages.length })
} catch (error) {
console.error('Windows sync failed:', error)
syncResults.push({ platform: 'windows', status: 'failed', error: error instanceof Error ? error.message : 'Unknown error' })
}
try {
// Sync macOS packages (Homebrew)
console.log('Syncing macOS packages...')
const homebrewFetcher = new HomebrewPackageFetcher()
const homebrewPackages = await homebrewFetcher.fetchAllPackages()
await homebrewFetcher.storePackages(homebrewPackages)
syncResults.push({ platform: 'macos', status: 'success', package_count: homebrewPackages.length })
} catch (error) {
console.error('macOS sync failed:', error)
syncResults.push({ platform: 'macos', status: 'failed', error: error instanceof Error ? error.message : 'Unknown error' })
}
try {
// Sync Fedora packages
console.log('Syncing Fedora packages...')
const fedoraFetcher = new FedoraPackageFetcher()
const fedoraPackages = await fedoraFetcher.fetchAllPackages()
await fedoraFetcher.storePackages(fedoraPackages)
syncResults.push({ platform: 'fedora', status: 'success', package_count: fedoraPackages.length })
} catch (error) {
console.error('Fedora sync failed:', error)
syncResults.push({ platform: 'fedora', status: 'failed', error: error instanceof Error ? error.message : 'Unknown error' })
}
try {
// Sync Arch packages
console.log('Syncing Arch packages...')
const archFetcher = new ArchPackageFetcher()
const archPackages = await archFetcher.fetchAllPackages()
await archFetcher.storePackages(archPackages)
syncResults.push({ platform: 'arch', status: 'success', package_count: archPackages.length })
} catch (error) {
console.error('Arch sync failed:', error)
syncResults.push({ platform: 'arch', status: 'failed', error: error instanceof Error ? error.message : 'Unknown error' })
}
// Update last sync time
lastAutoSync = now
const nextSync = SyncAuth.getNextSyncTime(lastAutoSync)
const successCount = syncResults.filter(r => r.status === 'success').length
const totalCount = syncResults.length
console.log(`✅ Auto sync completed: ${successCount}/${totalCount} platforms synced successfully`)
return NextResponse.json({
message: 'Auto sync completed',
timestamp: now.toISOString(),
last_sync: lastAutoSync.toISOString(),
next_sync: nextSync.toISOString(),
results: syncResults,
summary: {
total_platforms: totalCount,
successful: successCount,
failed: totalCount - successCount
}
})
} catch (error) {
console.error('Auto sync failed:', error)
return NextResponse.json(
{
error: 'Auto sync failed',
details: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString()
},
{ status: 500 }
)
}
}
export async function GET() {
const now = new Date()
const nextSyncTime = SyncAuth.getNextSyncTime(lastAutoSync || undefined)
return NextResponse.json({
auto_sync_enabled: SyncAuth.isAutoSyncEnabled(),
auto_sync_days: SyncAuth.getAutoSyncDays(),
last_sync: lastAutoSync?.toISOString(),
next_sync: SyncAuth.isAutoSyncEnabled() ? nextSyncTime.toISOString() : null,
status: lastAutoSync && now < nextSyncTime ? 'waiting' : 'ready'
})
}
+12 -2
View File
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { ArchPackageFetcher } from '@/services/archPackageFetcher' import { ArchPackageFetcher } from '@/services/archPackageFetcher'
import { SyncAuth } from '@/lib/sync/auth'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const maxDuration = 300 // 5 minutes timeout export const maxDuration = 300 // 5 minutes timeout
@@ -19,7 +20,16 @@ export async function GET() {
return NextResponse.json(syncStatus) return NextResponse.json(syncStatus)
} }
export async function POST() { export async function POST(request: NextRequest) {
// Check if sync is allowed
const authResult = await SyncAuth.isSyncAllowed(request)
if (!authResult.allowed) {
return NextResponse.json(
{ error: 'Sync operation not allowed', reason: authResult.reason },
{ status: 403 }
)
}
if (syncInProgress) { if (syncInProgress) {
return NextResponse.json( return NextResponse.json(
{ error: 'Sync already in progress' }, { error: 'Sync already in progress' },
+12 -2
View File
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { FedoraPackageFetcher } from '@/services/fedoraPackageFetcher' import { FedoraPackageFetcher } from '@/services/fedoraPackageFetcher'
import { SyncAuth } from '@/lib/sync/auth'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const maxDuration = 300 // 5 minutes timeout export const maxDuration = 300 // 5 minutes timeout
@@ -19,7 +20,16 @@ export async function GET() {
return NextResponse.json(syncStatus) return NextResponse.json(syncStatus)
} }
export async function POST() { export async function POST(request: NextRequest) {
// Check if sync is allowed
const authResult = await SyncAuth.isSyncAllowed(request)
if (!authResult.allowed) {
return NextResponse.json(
{ error: 'Sync operation not allowed', reason: authResult.reason },
{ status: 403 }
)
}
if (syncInProgress) { if (syncInProgress) {
return NextResponse.json( return NextResponse.json(
{ error: 'Sync already in progress' }, { error: 'Sync already in progress' },
+12 -2
View File
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { HomebrewPackageFetcher } from '@/services/homebrewPackageFetcher' import { HomebrewPackageFetcher } from '@/services/homebrewPackageFetcher'
import { SyncAuth } from '@/lib/sync/auth'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const maxDuration = 300 // 5 minutes timeout export const maxDuration = 300 // 5 minutes timeout
@@ -19,7 +20,16 @@ export async function GET() {
return NextResponse.json(syncStatus) return NextResponse.json(syncStatus)
} }
export async function POST() { export async function POST(request: NextRequest) {
// Check if sync is allowed
const authResult = await SyncAuth.isSyncAllowed(request)
if (!authResult.allowed) {
return NextResponse.json(
{ error: 'Sync operation not allowed', reason: authResult.reason },
{ status: 403 }
)
}
if (syncInProgress) { if (syncInProgress) {
return NextResponse.json( return NextResponse.json(
{ error: 'Sync already in progress' }, { error: 'Sync already in progress' },
+12 -2
View File
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import { WingetPackageFetcher } from '@/services/wingetPackageFetcher' import { WingetPackageFetcher } from '@/services/wingetPackageFetcher'
import { SyncAuth } from '@/lib/sync/auth'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const maxDuration = 300 // 5 minutes timeout export const maxDuration = 300 // 5 minutes timeout
@@ -19,7 +20,16 @@ export async function GET() {
return NextResponse.json(syncStatus) return NextResponse.json(syncStatus)
} }
export async function POST() { export async function POST(request: NextRequest) {
// Check if sync is allowed
const authResult = await SyncAuth.isSyncAllowed(request)
if (!authResult.allowed) {
return NextResponse.json(
{ error: 'Sync operation not allowed', reason: authResult.reason },
{ status: 403 }
)
}
if (syncInProgress) { if (syncInProgress) {
return NextResponse.json( return NextResponse.json(
{ error: 'Sync already in progress' }, { error: 'Sync already in progress' },
+16 -2
View File
@@ -4,8 +4,17 @@ import { DebianPackageFetcher } from '@/services/debianPackageFetcher'
import { PackageFetcherV2 } from '@/services/packageFetcherV2' import { PackageFetcherV2 } from '@/services/packageFetcherV2'
import { SimplePackageFetcher } from '@/services/simplePackageFetcher' import { SimplePackageFetcher } from '@/services/simplePackageFetcher'
import { PlatformInitializer } from '@/services/platformInitializer' import { PlatformInitializer } from '@/services/platformInitializer'
import { SyncAuth } from '@/lib/sync/auth'
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
// Check if sync is allowed
const authResult = await SyncAuth.isSyncAllowed(request)
if (!authResult.allowed) {
return NextResponse.json(
{ error: 'Sync operation not allowed', reason: authResult.reason },
{ status: 403 }
)
}
try { try {
const body = await request.json() const body = await request.json()
const { platform_id, all_platforms, source } = body const { platform_id, all_platforms, source } = body
@@ -107,11 +116,16 @@ export async function POST(request: NextRequest) {
export async function GET() { export async function GET() {
try { try {
// Return sync status (would need to implement status tracking) // Return sync status with configuration info
return NextResponse.json({ return NextResponse.json({
status: 'ready', status: 'ready',
last_sync: null, last_sync: null,
platforms: ['ubuntu', 'fedora', 'arch', 'windows', 'macos'] platforms: ['ubuntu', 'fedora', 'arch', 'windows', 'macos'],
sync_config: {
server_only: process.env.SYNC_SERVER_ONLY === 'true',
auto_sync_enabled: SyncAuth.isAutoSyncEnabled(),
auto_sync_days: SyncAuth.getAutoSyncDays()
}
}) })
} catch (error) { } catch (error) {
console.error('Error getting sync status:', error) console.error('Error getting sync status:', error)
+86
View File
@@ -0,0 +1,86 @@
import { NextRequest } from 'next/server'
export class SyncAuth {
private static readonly SERVER_ONLY = process.env.SYNC_SERVER_ONLY === 'true'
private static readonly SYNC_SECRET = process.env.SYNC_SECRET_KEY
/**
* Check if sync operations are allowed from the current request
*/
static async isSyncAllowed(request: NextRequest): Promise<{ allowed: boolean; reason?: string }> {
// If server-only mode is disabled, allow all requests
if (!this.SERVER_ONLY) {
return { allowed: true }
}
// In server-only mode, check for secret key in header
const secretKey = request.headers.get('x-sync-secret')
if (!this.SYNC_SECRET) {
return {
allowed: false,
reason: 'Sync secret key not configured on server'
}
}
if (!secretKey) {
return {
allowed: false,
reason: 'Sync secret key required in server-only mode'
}
}
if (secretKey !== this.SYNC_SECRET) {
return {
allowed: false,
reason: 'Invalid sync secret key'
}
}
// Additional check: verify request is from localhost or same server
const clientIP = request.headers.get('x-forwarded-for') ||
request.headers.get('x-real-ip') ||
'unknown'
const allowedIPs = ['127.0.0.1', 'localhost', '::1']
const isLocalRequest = allowedIPs.includes(clientIP.split(',')[0].trim())
if (!isLocalRequest && secretKey !== this.SYNC_SECRET) {
return {
allowed: false,
reason: 'Sync operations only allowed from server in server-only mode'
}
}
return { allowed: true }
}
/**
* Get automatic sync frequency in days
*/
static getAutoSyncDays(): number {
const days = parseInt(process.env.AUTO_SYNC_DAYS || '1', 10)
return isNaN(days) ? 1 : Math.max(0, days)
}
/**
* Check if automatic sync is enabled
*/
static isAutoSyncEnabled(): boolean {
return this.getAutoSyncDays() > 0
}
/**
* Get the next sync time based on frequency
*/
static getNextSyncTime(lastSyncTime?: Date): Date {
const days = this.getAutoSyncDays()
if (days === 0) {
return new Date(0) // Return epoch time if disabled
}
const nextSync = new Date(lastSyncTime || new Date())
nextSync.setDate(nextSync.getDate() + days)
return nextSync
}
}