feat: Google Drive backup OAuth (Faz 1 — backend)

PRD "AyrisLegal Google Drive Backup & Recovery" kapsamında sadece
backend altyapısı: OAuth connect/callback/status/disconnect/refresh
uç noktaları, AES-256-GCM token şifreleme (tokenCrypto.ts), tek
kullanımlık CSRF state (oauthState.ts), drive.file scope ile
AyrisLegal/Davalar klasör oluşturma (googleDriveClient.ts). Electron
tarafı (dosya tarama/kuyruk/UI) ayrı bir aşamada.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mstfyldz
2026-08-10 03:51:02 +03:00
co-authored by Claude Sonnet 5
parent 9618ff4513
commit 328aef91bd
6 changed files with 430 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
import crypto from 'crypto';
// Google OAuth "state" parametresi: CSRF'e karşı kriptografik olarak rastgele,
// kısa ömürlü, tek kullanımlık (bkz. PRD §20). Bellek-içi Map yeterli — OAuth
// akışı saniyeler/dakikalar içinde tamamlanıyor, kalıcı depolamaya gerek yok.
// NOT: backend birden fazla instance'la (load balancer arkasında) çalışırsa bu
// state farklı bir instance'a düşebilir — o zaman paylaşımlı bir store (Redis
// vb.) gerekir. Şu an tek instance varsayımıyla yazıldı.
interface StateEntry { userId: string; expiresAt: number; }
const STATE_TTL_MS = 10 * 60 * 1000; // 10 dakika
const stateStore = new Map<string, StateEntry>();
function cleanupExpired() {
const now = Date.now();
for (const [key, entry] of stateStore.entries()) {
if (entry.expiresAt < now) stateStore.delete(key);
}
}
export function createState(userId: string): string {
cleanupExpired();
const state = crypto.randomBytes(32).toString('hex');
stateStore.set(state, { userId, expiresAt: Date.now() + STATE_TTL_MS });
return state;
}
// Tek kullanımlık — çağrıldığı anda store'dan siliniyor, ikinci kullanım (replay)
// her zaman null döner.
export function consumeState(state: string): string | null {
const entry = stateStore.get(state);
if (!entry) return null;
stateStore.delete(state);
if (entry.expiresAt < Date.now()) return null;
return entry.userId;
}