Spaces:
Sleeping
Sleeping
| import * as crypto from 'crypto'; | |
| import { config } from '../config'; | |
| const ALGORITHM = 'aes-256-gcm'; | |
| const IV_LENGTH = 16; | |
| const AUTH_TAG_LENGTH = 16; | |
| /** | |
| * Derive a 32-byte key from the encryption key string. | |
| * Uses SHA-256 so any length passphrase works. | |
| */ | |
| function deriveKey(): Buffer { | |
| return crypto.createHash('sha256').update(config.credentialEncryptionKey).digest(); | |
| } | |
| /** | |
| * Encrypt a plaintext password. | |
| * Returns base64 string: iv(16) + authTag(16) + ciphertext | |
| */ | |
| export function encryptPassword(plaintext: string): string { | |
| const key = deriveKey(); | |
| const iv = crypto.randomBytes(IV_LENGTH); | |
| const cipher = crypto.createCipheriv(ALGORITHM, key, iv); | |
| const encrypted = Buffer.concat([ | |
| cipher.update(plaintext, 'utf8'), | |
| cipher.final(), | |
| ]); | |
| const authTag = cipher.getAuthTag(); | |
| // Format: iv + authTag + ciphertext | |
| const combined = Buffer.concat([iv, authTag, encrypted]); | |
| return combined.toString('base64'); | |
| } | |
| /** | |
| * Decrypt an encrypted password string. | |
| * Input is base64 string: iv(16) + authTag(16) + ciphertext | |
| */ | |
| export function decryptPassword(encrypted: string): string { | |
| const key = deriveKey(); | |
| const combined = Buffer.from(encrypted, 'base64'); | |
| const iv = combined.subarray(0, IV_LENGTH); | |
| const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH); | |
| const ciphertext = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH); | |
| const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); | |
| decipher.setAuthTag(authTag); | |
| const decrypted = Buffer.concat([ | |
| decipher.update(ciphertext), | |
| decipher.final(), | |
| ]); | |
| return decrypted.toString('utf8'); | |
| } | |
| /** | |
| * Get the Turnitin password for an account. | |
| * If encryption key is set, tries to decrypt. Otherwise uses shared password. | |
| */ | |
| export function getAccountPassword(encryptedPassword?: string | null): string { | |
| if (encryptedPassword && config.credentialEncryptionKey) { | |
| try { | |
| return decryptPassword(encryptedPassword); | |
| } catch { | |
| // Fallback to shared password if decryption fails | |
| } | |
| } | |
| return config.turnitinSharedPassword; | |
| } | |