import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { createCipheriv, createDecipheriv, createHash, randomBytes, } from 'crypto'; @Injectable() export class TokenCryptoService { private readonly key: Buffer; constructor(configService: ConfigService) { const configuredKey = configService.get('APP_ENCRYPTION_KEY'); if (!configuredKey) { throw new Error( 'Missing required environment variable: APP_ENCRYPTION_KEY', ); } this.key = createHash('sha256').update(configuredKey).digest(); } encrypt(value: string): string { const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', this.key, iv); const encrypted = Buffer.concat([ cipher.update(value, 'utf8'), cipher.final(), ]); const authTag = cipher.getAuthTag(); return [ iv.toString('base64url'), authTag.toString('base64url'), encrypted.toString('base64url'), ].join('.'); } decrypt(value: string): string { const [ivValue, authTagValue, encryptedValue] = value.split('.'); if (!ivValue || !authTagValue || !encryptedValue) { throw new Error('Invalid encrypted token format'); } const decipher = createDecipheriv( 'aes-256-gcm', this.key, Buffer.from(ivValue, 'base64url'), ); decipher.setAuthTag(Buffer.from(authTagValue, 'base64url')); return Buffer.concat([ decipher.update(Buffer.from(encryptedValue, 'base64url')), decipher.final(), ]).toString('utf8'); } }