Files
Momento/memento-note/lib/crypto.ts
Antigravity bd495be965
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
2026-05-16 12:59:30 +00:00

66 lines
1.9 KiB
TypeScript

import {
createCipheriv,
createDecipheriv,
createHash,
randomBytes,
scrypt,
} from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY_LEN = 32;
const IV_LEN = 16;
const SALT_LEN = 16;
const TAG_LEN = 16;
function getMasterPassphrase(): string {
const master = process.env.MASTER_ENCRYPTION_KEY;
if (!master || master.length < 32) {
throw new Error('MASTER_ENCRYPTION_KEY must be set (minimum 32 characters)');
}
return master;
}
async function deriveKey(salt: Buffer): Promise<Buffer> {
return new Promise((resolve, reject) => {
scrypt(getMasterPassphrase(), salt, KEY_LEN, (err, key) => {
if (err) reject(err);
else resolve(key);
});
});
}
export async function encryptApiKey(plaintext: string): Promise<string> {
const salt = randomBytes(SALT_LEN);
const iv = randomBytes(IV_LEN);
const key = await deriveKey(salt);
const cipher = createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
}
export async function decryptApiKey(payload: string): Promise<string> {
const buf = Buffer.from(payload, 'base64');
if (buf.length < SALT_LEN + IV_LEN + TAG_LEN + 1) {
throw new Error('Invalid encrypted key payload');
}
const salt = buf.subarray(0, SALT_LEN);
const iv = buf.subarray(SALT_LEN, SALT_LEN + IV_LEN);
const tag = buf.subarray(SALT_LEN + IV_LEN, SALT_LEN + IV_LEN + TAG_LEN);
const ciphertext = buf.subarray(SALT_LEN + IV_LEN + TAG_LEN);
const key = await deriveKey(salt);
const decipher = createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString('utf8');
}
export function hashApiKey(plaintext: string): string {
return createHash('sha256').update(plaintext, 'utf8').digest('hex');
}