Svo znanje
Learning: Encrypted API keys vault (AES-256-GCM u Supabase)
protos-web-api-keys-vault·Protos-Web
api-keysencryptionaes-256-gcmsupabaserlsvault
Learning: Encrypted API keys vault (AES-256-GCM u Supabase)
Kontekst: Protos-Web /admin/kljucevi — user je htio spremati stvarne API ključeve za razne servise (Sketchfab, OpenAI itd.) tako da ih server-side code može čitati bez izlaganja anon roli.
Model
Plaintext key (user submits kroz admin UI)
│
▼
encryptSecret(k, master = env ADMIN_KEYS_ENCRYPTION_KEY)
│
├── ciphertext (base64) ─┐
├── iv (base64) ────────┤─→ INSERT INTO admin_api_keys (...)
└── auth_tag (base64) ───┘ [RLS: service_role only, nikad anon]
│
▼
maskSecret(k) → "sk_l...wxyz" → stored kao masked_hint (safe za UI list view)
Reveal flow (samo server-side actions iza requireAdmin()):
requireAdmin() → serviceRole.select(row) → decryptSecret(...) → plaintext
│
└─ stamp last_used_at = now() (audit)
Ključne komponente
src/lib/security/api-keys-crypto.ts—encryptSecret,decryptSecret,maskSecret,getMasterKeysrc/lib/config/api-key-providers.ts— registry svih poznatih providera (OpenAI, Stripe, Sketchfab, DeepSeek, Gemini, itd.) s label/category/docsUrl/envHintsrc/lib/schemas/api-key.ts— Zod schemas (create/update)src/lib/queries/admin/api-keys.ts—listAdminApiKeys,create,update,delete,revealAdminApiKey,getActiveApiKeysrc/actions/admin-api-keys.ts— server actions srequireAdmin()+ Zod validationsrc/types/admin-api-keys.ts— list item / form / update types (nikad ne exposeaju ciphertext klijentu)- DB tablica
admin_api_keys(migracijasupabase/migrations/20260720062823_admin_api_keys.sql)
Pravila
- Master key = env
ADMIN_KEYS_ENCRYPTION_KEY= base64 32 bytes. Generira se JEDNOM:node -e "console.log(require('crypto').randomBytes(32).toString('base64'))". Postavi ISTU vrijednost u Vercel Production + Preview + Development. - Gubitak master key-a = svi vault entries nečitljivi. Backup master key IZVAN Vercela (password manager, sigurni tref).
- RLS
service_role only— nikadanoniliauthenticatedne smiju čitati tablicu. Client komponente pristupaju SAMO kroz server actions. masked_hintje jedino što UI list view pokazuje bez reveal action-a. Puni plaintext samo poslije eksplicitnog "Otkrij" gumba (koji zahtijeva requireAdmin i loga ulast_used_at).getActiveApiKey(provider)— convenience za druge server modele:const key = await getActiveApiKey('sketchfab')— koristi ovo umjesto direktnogprocess.env.SKETCHFAB_API_TOKENkada je moguće (fallback pattern usrc/lib/config/sketchfab.ts).
UI
- Warning banner na
/admin/kljuceviakoADMIN_KEYS_ENCRYPTION_KEYnedostaje (aplikacija ne pada, blokira samo reveal) - Grupiranje po providerskoj kategoriji
- Toast notifikacije na create/update/delete/reveal preko
useToastStore
Isti model za automation webhooks
Identičan pattern se koristi u automation_webhooks tablici — auth vrijednost (bearer/basic/custom header) šifrirana istim master key-om (auth_ciphertext, auth_iv, auth_tag).
Kada koristiti (i kada ne)
Da:
- API ključevi trećih strana koje admin panel poziva server-side (Sketchfab, LLM providers, image APIs)
- Webhook autorizacija (šalju se u outbound HTTP)
- Svaki secret koji admin sam registrira kroz UI
Ne:
- Core infra secrets (
SUPABASE_SERVICE_ROLE_KEY,STRIPE_WEBHOOK_SECRET,NEXT_PUBLIC_*) — ostaju u Vercelu/Supabase Edge, ne u vault-u - Client-side ključevi (nema smisla — anon key namijenjen javnosti)
Zapamti
- Sve nove providerske integracije koje trebaju API key preferiraju vault (
getActiveApiKey) uz fallback naprocess.env.*— vidisrc/lib/config/sketchfab.tskao referencu. - Never expose ciphertext klijentu. Types u
src/types/admin-api-keys.tssu namjerno tako složeni.