mirror of
https://github.com/Tony0410/quietthanks.git
synced 2026-05-24 21:31:41 +08:00
Enhance security: Rotate VAPID keys, encrypt LLM API keys, and use env vars
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { db, schema } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { eq, and, gte, lte, desc } from "drizzle-orm";
|
||||
import { decrypt } from "@/lib/crypto";
|
||||
|
||||
// POST /api/reflections - Generate an LLM reflection for a time period
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -31,7 +32,8 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const { llmProvider, llmApiKey, llmModel } = users[0];
|
||||
const { llmProvider, llmModel } = users[0];
|
||||
const llmApiKey = decrypt(users[0].llmApiKey);
|
||||
|
||||
// Calculate date range
|
||||
const now = new Date();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import { db, schema } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { encrypt } from "@/lib/crypto";
|
||||
|
||||
// GET /api/settings - Get current user settings
|
||||
export async function GET() {
|
||||
@@ -70,7 +71,8 @@ export async function PATCH(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (typeof body.llmApiKey === "string") {
|
||||
updates.llmApiKey = body.llmApiKey || null;
|
||||
// Encrypt the API key before storing
|
||||
updates.llmApiKey = body.llmApiKey ? encrypt(body.llmApiKey) : null;
|
||||
}
|
||||
|
||||
if (typeof body.llmModel === "string") {
|
||||
|
||||
55
src/lib/crypto.ts
Normal file
55
src/lib/crypto.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
||||
|
||||
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '';
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
|
||||
export function encrypt(text: string): string {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
console.warn('ENCRYPTION_KEY not set, storing in plain text');
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
const iv = randomBytes(16);
|
||||
const key = Buffer.from(ENCRYPTION_KEY, 'hex');
|
||||
const cipher = createCipheriv(ALGORITHM, key, iv);
|
||||
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
const authTag = cipher.getAuthTag().toString('hex');
|
||||
|
||||
// Format: iv:authTag:encrypted
|
||||
return `${iv.toString('hex')}:${authTag}:${encrypted}`;
|
||||
} catch (error) {
|
||||
console.error('Encryption failed:', error);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
export function decrypt(text: string): string {
|
||||
if (!ENCRYPTION_KEY || !text) return text;
|
||||
|
||||
const parts = text.split(':');
|
||||
// If not in encrypted format (legacy plain text), return as is
|
||||
if (parts.length !== 3) return text;
|
||||
|
||||
try {
|
||||
const [ivHex, authTagHex, encryptedHex] = parts;
|
||||
const key = Buffer.from(ENCRYPTION_KEY, 'hex');
|
||||
const iv = Buffer.from(ivHex, 'hex');
|
||||
const authTag = Buffer.from(authTagHex, 'hex');
|
||||
|
||||
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
} catch (error) {
|
||||
// If decryption fails, assume it might be plain text or corrupted
|
||||
console.warn('Decryption failed, returning original text');
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user