diff --git a/lib/crypto.ts b/lib/crypto.ts new file mode 100644 index 0000000..1e45d2c --- /dev/null +++ b/lib/crypto.ts @@ -0,0 +1,33 @@ +import { createCipheriv, createDecipheriv, randomBytes } from "crypto"; + +const ALGORITHM = "aes-256-gcm"; +const IV_BYTES = 12; +const TAG_BYTES = 16; + +function getKey(): Buffer { + const hex = process.env.GARMIN_ENCRYPTION_KEY; + if (!hex || hex.length !== 64) { + throw new Error("Brak lub nieprawidłowy GARMIN_ENCRYPTION_KEY w konfiguracji."); + } + return Buffer.from(hex, "hex"); +} + +export function encrypt(plaintext: string): string { + const key = getKey(); + const iv = randomBytes(IV_BYTES); + const cipher = createCipheriv(ALGORITHM, key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + const tag = cipher.getAuthTag(); + // Format: iv(hex):tag(hex):ciphertext(hex) + return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`; +} + +export function decrypt(stored: string): string { + const parts = stored.split(":"); + if (parts.length !== 3) throw new Error("Nieprawidłowy format zaszyfrowanego hasła."); + const [ivHex, tagHex, dataHex] = parts; + const key = getKey(); + const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(ivHex, "hex")); + decipher.setAuthTag(Buffer.from(tagHex, "hex")); + return decipher.update(Buffer.from(dataHex, "hex")).toString("utf8") + decipher.final("utf8"); +} diff --git a/lib/models/garmin-auth.ts b/lib/models/garmin-auth.ts index 31edaac..3583a7d 100644 --- a/lib/models/garmin-auth.ts +++ b/lib/models/garmin-auth.ts @@ -1,5 +1,6 @@ import type { IOauth1Token } from "garmin-connect/dist/garmin/types"; import { getDb } from "@/lib/db"; +import { encrypt, decrypt } from "@/lib/crypto"; import type { GarminPendingMfa } from "@/lib/garmin/sso"; const AUTH_COLLECTION = "garmin_auth"; @@ -49,7 +50,7 @@ export async function getGarminCredentials( .collection(CREDENTIALS_COLLECTION) .findOne({ _id: userId }); if (!doc) return null; - return { email: doc.email, password: doc.password }; + return { email: doc.email, password: decrypt(doc.password) }; } export async function saveGarminCredentials( @@ -60,5 +61,5 @@ export async function saveGarminCredentials( const db = await getDb(); await db .collection(CREDENTIALS_COLLECTION) - .updateOne({ _id: userId }, { $set: { email, password } }, { upsert: true }); + .updateOne({ _id: userId }, { $set: { email, password: encrypt(password) } }, { upsert: true }); }