80 lines
2.1 KiB
JavaScript
80 lines
2.1 KiB
JavaScript
const crypto = require('crypto')
|
|
|
|
const ALGORITHM = 'aes-256-gcm'
|
|
const IV_LENGTH = 12
|
|
const SALT = 'modstagram-instagram-salt'
|
|
|
|
let cachedKey = null
|
|
|
|
function getKey () {
|
|
if (cachedKey) return cachedKey
|
|
|
|
const secret = process.env.INSTAGRAM_TOKEN_ENC_KEY || process.env.APP_SECRET
|
|
if (!secret) {
|
|
throw new Error(
|
|
'[secretCipher] Neither INSTAGRAM_TOKEN_ENC_KEY nor APP_SECRET is configured'
|
|
)
|
|
}
|
|
|
|
if (!process.env.INSTAGRAM_TOKEN_ENC_KEY) {
|
|
console.warn(
|
|
'[secretCipher] INSTAGRAM_TOKEN_ENC_KEY is not set; falling back to APP_SECRET. Set a dedicated key in production.'
|
|
)
|
|
}
|
|
|
|
cachedKey = crypto.scryptSync(secret, SALT, 32)
|
|
return cachedKey
|
|
}
|
|
|
|
/**
|
|
* Encrypt a plaintext secret (e.g. Instagram access token) for at-rest storage.
|
|
* Returns `${iv}:${authTag}:${ciphertext}` (all hex), or null for empty input.
|
|
*/
|
|
function encryptSecret (plaintext) {
|
|
if (!plaintext) return null
|
|
|
|
const key = getKey()
|
|
const iv = crypto.randomBytes(IV_LENGTH)
|
|
const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
|
|
|
|
const encrypted = Buffer.concat([
|
|
cipher.update(String(plaintext), 'utf8'),
|
|
cipher.final()
|
|
])
|
|
const authTag = cipher.getAuthTag()
|
|
|
|
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`
|
|
}
|
|
|
|
/**
|
|
* Decrypt a value produced by encryptSecret. Returns null on missing/invalid input
|
|
* instead of throwing, so a corrupt/legacy value never crashes a caller.
|
|
*/
|
|
function decryptSecret (encoded) {
|
|
if (!encoded || typeof encoded !== 'string') return null
|
|
|
|
const parts = encoded.split(':')
|
|
if (parts.length !== 3) return null
|
|
|
|
try {
|
|
const [ivHex, authTagHex, dataHex] = parts
|
|
const key = getKey()
|
|
const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(ivHex, 'hex'))
|
|
decipher.setAuthTag(Buffer.from(authTagHex, 'hex'))
|
|
|
|
const decrypted = Buffer.concat([
|
|
decipher.update(Buffer.from(dataHex, 'hex')),
|
|
decipher.final()
|
|
])
|
|
return decrypted.toString('utf8')
|
|
} catch (err) {
|
|
console.error('[secretCipher] decrypt failed:', err.message)
|
|
return null
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
encryptSecret,
|
|
decryptSecret
|
|
}
|