|
import * as crypto from "crypto"; |
|
import Marshal from "marshal"; |
|
|
|
function decrypt(cookie: string): Object { |
|
const unescapedCookie = unescape(cookie); |
|
|
|
if (!process.env.RAILS_SECRET_KEY_BASE) { |
|
throw new Error("No Exists process.env.RAILS_SECRET_KEY_BASE"); |
|
} |
|
const secretKeyBase = process.env.RAILS_SECRET_KEY_BASE; |
|
const encryptedCookieSalt = "encrypted cookie"; |
|
const encryptedSignedCookieSalt = "signed encrypted cookie"; |
|
const iterations = 1000; |
|
const keySize = 64; |
|
const secret = crypto.pbkdf2Sync(secretKeyBase, encryptedCookieSalt, iterations, keySize / 2, "SHA1"); |
|
const signedSecret = crypto.pbkdf2Sync(secretKeyBase, encryptedSignedCookieSalt, iterations, keySize, "SHA1"); |
|
|
|
const [data, digest] = unescapedCookie.split("--"); |
|
|
|
// raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data) |
|
// # you better use secure compare instead of `==` to prevent time based attact, |
|
// # ref: ActiveSupport::SecurityUtils.secure_compare |
|
|
|
const encryptedMessage = Buffer.from(data, "base64").toString(); |
|
const [encryptedData, iv] = encryptedMessage.split("--").map(message => { |
|
return Buffer.from(message, "base64"); |
|
}); |
|
|
|
const decipher = crypto.createDecipheriv("aes-256-cbc", secret, iv); |
|
let dec = decipher.update(encryptedData); |
|
dec = Buffer.concat([dec, decipher.final()]); |
|
|
|
const marshal = new Marshal(); |
|
const parsed = marshal.load(dec).parsed; |
|
|
|
return parsed; |
|
} |
|
|
|
export const setDecryptCookies = (cookieName: string) => { |
|
return (req, _, next) => { |
|
if (req.cookies && req.cookies[cookieName]) { |
|
// Base64 encode & 暗号化 された文字列をオブジェクトへ変換 |
|
req.cookies[cookieName] = decrypt(req.cookies[cookieName]); |
|
next(); |
|
} else { |
|
next(); |
|
} |
|
}; |
|
}; |