56 lines
1.9 KiB
JavaScript
56 lines
1.9 KiB
JavaScript
import crypto from 'node:crypto'
|
|
|
|
const configuredKey = process.env.MEMBERSHIP_ENCRYPTION_KEY
|
|
const fallbackKey = '0000000000000000000000000000000000000000000000000000000000000000'
|
|
|
|
if (!configuredKey) {
|
|
console.warn('MEMBERSHIP_ENCRYPTION_KEY is not set. Using a development fallback key.')
|
|
}
|
|
|
|
const getKey = () => {
|
|
const key = Buffer.from(configuredKey || fallbackKey, 'hex')
|
|
if (key.length !== 32) {
|
|
throw new Error('MEMBERSHIP_ENCRYPTION_KEY must be 64 hex characters.')
|
|
}
|
|
return key
|
|
}
|
|
|
|
export const encryptField = (value = '') => {
|
|
if (!value) return ''
|
|
const iv = crypto.randomBytes(16)
|
|
const cipher = crypto.createCipheriv('aes-256-gcm', getKey(), iv)
|
|
const encrypted = Buffer.concat([cipher.update(String(value), 'utf8'), cipher.final()])
|
|
const tag = cipher.getAuthTag()
|
|
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`
|
|
}
|
|
|
|
export const decryptField = (value = '') => {
|
|
if (!value || !String(value).includes(':')) return value || ''
|
|
const parts = String(value).split(':')
|
|
if (parts.length !== 3 || parts[0].length !== 32 || parts[1].length !== 32) return value
|
|
|
|
const [ivHex, tagHex, encryptedHex] = parts
|
|
const decipher = crypto.createDecipheriv('aes-256-gcm', getKey(), Buffer.from(ivHex, 'hex'))
|
|
decipher.setAuthTag(Buffer.from(tagHex, 'hex'))
|
|
return Buffer.concat([
|
|
decipher.update(Buffer.from(encryptedHex, 'hex')),
|
|
decipher.final(),
|
|
]).toString('utf8')
|
|
}
|
|
|
|
export const encryptMembership = (member) => ({
|
|
...member,
|
|
fullName: encryptField(member.fullName),
|
|
email: encryptField(member.email || ''),
|
|
phone: encryptField(member.phone || ''),
|
|
signature: encryptField(member.signature || ''),
|
|
})
|
|
|
|
export const decryptMembership = (member) => ({
|
|
...member,
|
|
fullName: decryptField(member.fullName),
|
|
email: decryptField(member.email || ''),
|
|
phone: decryptField(member.phone || ''),
|
|
signature: decryptField(member.signature || ''),
|
|
})
|