ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 139 lines 3.4 kB view raw
1import { createCipheriv, createDecipheriv, randomBytes } from "crypto"; 2import { ApiError } from "../errors"; 3 4/** 5 * Token Encryption Service 6 * Encrypts sensitive OAuth tokens at rest using AES-256-GCM 7 */ 8 9function getEncryptionKey(): Buffer { 10 const key = process.env.TOKEN_ENCRYPTION_KEY; 11 12 if (!key) { 13 throw new ApiError( 14 "Encryption key not configured", 15 500, 16 "TOKEN_ENCRYPTION_KEY environment variable is required", 17 ); 18 } 19 20 // Expect 64-char hex string (32 bytes) 21 if (key.length !== 64) { 22 throw new ApiError( 23 "Invalid encryption key", 24 500, 25 "TOKEN_ENCRYPTION_KEY must be 64 hex characters (32 bytes)", 26 ); 27 } 28 29 return Buffer.from(key, "hex"); 30} 31 32interface EncryptedPayload { 33 iv: string; 34 data: string; 35 tag: string; 36} 37 38/** 39 * Encrypt sensitive data using AES-256-GCM 40 * @param data - Data to encrypt (will be JSON stringified) 41 * @returns Encrypted payload as JSON string 42 */ 43export function encryptToken<T = unknown>(data: T): string { 44 try { 45 const key = getEncryptionKey(); 46 const iv = randomBytes(16); 47 48 const cipher = createCipheriv("aes-256-gcm", key, iv); 49 50 const jsonData = JSON.stringify(data); 51 const encrypted = Buffer.concat([ 52 cipher.update(jsonData, "utf8"), 53 cipher.final(), 54 ]); 55 56 const authTag = cipher.getAuthTag(); 57 58 const payload: EncryptedPayload = { 59 iv: iv.toString("hex"), 60 data: encrypted.toString("hex"), 61 tag: authTag.toString("hex"), 62 }; 63 64 return JSON.stringify(payload); 65 } catch (error) { 66 console.error("Token encryption failed:", error); 67 throw new ApiError( 68 "Failed to encrypt token", 69 500, 70 error instanceof Error ? error.message : "Unknown encryption error", 71 ); 72 } 73} 74 75/** 76 * Decrypt sensitive data 77 * @param encrypted - Encrypted payload as JSON string 78 * @returns Decrypted data 79 */ 80export function decryptToken<T = unknown>(encrypted: string): T { 81 try { 82 const key = getEncryptionKey(); 83 const payload: EncryptedPayload = JSON.parse(encrypted); 84 85 const decipher = createDecipheriv( 86 "aes-256-gcm", 87 key, 88 Buffer.from(payload.iv, "hex"), 89 ); 90 91 decipher.setAuthTag(Buffer.from(payload.tag, "hex")); 92 93 const decrypted = Buffer.concat([ 94 decipher.update(Buffer.from(payload.data, "hex")), 95 decipher.final(), 96 ]); 97 98 return JSON.parse(decrypted.toString("utf8")); 99 } catch (error) { 100 console.error("Token decryption failed:", error); 101 throw new ApiError( 102 "Failed to decrypt token", 103 500, 104 error instanceof Error ? error.message : "Unknown decryption error", 105 ); 106 } 107} 108 109/** 110 * Generate a new encryption key (for initial setup) 111 * Run this once and store in environment variables 112 */ 113export function generateEncryptionKey(): string { 114 return randomBytes(32).toString("hex"); 115} 116 117/** 118 * Check if encryption is properly configured 119 * Returns false in development if key is missing (with warning) 120 */ 121export function isEncryptionConfigured(): boolean { 122 const key = process.env.TOKEN_ENCRYPTION_KEY; 123 124 if (!key) { 125 if (process.env.NODE_ENV === "production") { 126 throw new ApiError( 127 "Encryption key not configured in production", 128 500, 129 "TOKEN_ENCRYPTION_KEY is required in production", 130 ); 131 } 132 console.warn( 133 "⚠️ TOKEN_ENCRYPTION_KEY not set - tokens will NOT be encrypted", 134 ); 135 return false; 136 } 137 138 return true; 139}