full
This commit is contained in:
79
utils/secretCipher.js
Normal file
79
utils/secretCipher.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user