Files
TREK/server/src/services/apiKeyCrypto.ts
T
Julien G. 905c7d460b Add comprehensive backend test suite (#339)
* add test suite, mostly covers integration testing, tests are only backend side

* workflow runs the correct script

* workflow runs the correct script

* workflow runs the correct script

* unit tests incoming

* Fix multer silent rejections and error handler info leak

- Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts,
  and files.ts so invalid uploads return an error instead of silently
  dropping the file
- Error handler in app.ts now always returns 500 / "Internal server
  error" instead of forwarding err.message to the client

* Use statusCode consistently for multer errors and error handler

- Error handler in app.ts reads err.statusCode to forward the correct
  HTTP status while keeping the response body generic
2026-04-03 13:17:53 +02:00

44 lines
1.5 KiB
TypeScript

import * as crypto from 'node:crypto';
import { ENCRYPTION_KEY } from '../config';
const ENCRYPTED_PREFIX = 'enc:v1:';
function get_key() {
return crypto.createHash('sha256').update(`${ENCRYPTION_KEY}:api_keys:v1`).digest();
}
export function encrypt_api_key(plain: unknown) {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', get_key(), iv);
const enc = Buffer.concat([cipher.update(String(plain), 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
const blob = Buffer.concat([iv, tag, enc]).toString('base64');
return `${ENCRYPTED_PREFIX}${blob}`;
}
export function decrypt_api_key(value: unknown) {
if (!value) return null;
if (typeof value !== 'string') return null;
if (!value.startsWith(ENCRYPTED_PREFIX)) return value; // legacy plaintext
const blob = value.slice(ENCRYPTED_PREFIX.length);
try {
const buf = Buffer.from(blob, 'base64');
const iv = buf.subarray(0, 12);
const tag = buf.subarray(12, 28);
const enc = buf.subarray(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', get_key(), iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8');
} catch {
return null;
}
}
export function maybe_encrypt_api_key(value: unknown) {
const trimmed = String(value || '').trim();
if (!trimmed) return null;
if (trimmed.startsWith(ENCRYPTED_PREFIX)) return trimmed;
return encrypt_api_key(trimmed);
}