feat: scaffold klarbild foundation (astro+postgres+s3, auth, migrations, health)
- Astro 5 SSR (node standalone) + React, OKLCH tokens (no tailwind) - migrations/001_init.sql: full schema per 03-datenmodell-api - lib: db+migrations, crypto (AES-256-GCM), auth (argon2+signed session, ratelimit), storage (S3/MinIO, presigned URLs), openrouter (POST /v1/images, cost) - middleware: init-once + session guard + admin gate; /api/health (db+storage) - login + studio placeholder; seeds (till/lea, default recipes); Dockerfile - verified: astro build passes
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
||||
|
||||
// AES-256-GCM. ENCRYPTION_KEY = 64 Hex-Zeichen (32 Byte).
|
||||
function key(): Buffer {
|
||||
const hex = process.env.ENCRYPTION_KEY || '';
|
||||
if (hex.length !== 64) {
|
||||
throw new Error('ENCRYPTION_KEY muss 64 Hex-Zeichen (32 Byte) lang sein');
|
||||
}
|
||||
return Buffer.from(hex, 'hex');
|
||||
}
|
||||
|
||||
/** Verschlüsselt Klartext → "iv.tag.ciphertext" (base64url-Teile). */
|
||||
export function encrypt(plain: string): string {
|
||||
const iv = randomBytes(12);
|
||||
const cipher = createCipheriv('aes-256-gcm', key(), iv);
|
||||
const enc = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
|
||||
const tag = cipher.getAuthTag();
|
||||
return [iv, tag, enc].map((b) => b.toString('base64url')).join('.');
|
||||
}
|
||||
|
||||
export function decrypt(payload: string): string {
|
||||
const [ivB, tagB, dataB] = payload.split('.');
|
||||
const decipher = createDecipheriv('aes-256-gcm', key(), Buffer.from(ivB, 'base64url'));
|
||||
decipher.setAuthTag(Buffer.from(tagB, 'base64url'));
|
||||
return Buffer.concat([
|
||||
decipher.update(Buffer.from(dataB, 'base64url')),
|
||||
decipher.final(),
|
||||
]).toString('utf8');
|
||||
}
|
||||
|
||||
/** Maskiert einen Schlüssel für die Anzeige: sk-or-…4f2a */
|
||||
export function maskSecret(secret: string): string {
|
||||
if (!secret) return '';
|
||||
const tail = secret.slice(-4);
|
||||
const head = secret.slice(0, Math.min(5, secret.length));
|
||||
return `${head}…${tail}`;
|
||||
}
|
||||
Reference in New Issue
Block a user