Spaces:
Running
Running
| const crypto = require("node:crypto"); | |
| function toBase64Url(buffer) { | |
| return Buffer.from(buffer).toString("base64url"); | |
| } | |
| function fromBase64Url(value) { | |
| return Buffer.from(value, "base64url"); | |
| } | |
| function deriveMasterKey(encryptionKey) { | |
| return crypto.scryptSync(encryptionKey, "hf-home-master-key", 32); | |
| } | |
| function deriveTokenKey(masterKey, sessionSecret, sessionId) { | |
| return crypto.hkdfSync( | |
| "sha256", | |
| Buffer.from(sessionSecret, "utf8"), | |
| masterKey, | |
| Buffer.from(`hf-home:${sessionId}`, "utf8"), | |
| 32, | |
| ); | |
| } | |
| function encryptText(plainText, key, aad = "") { | |
| const iv = crypto.randomBytes(12); | |
| const cipher = crypto.createCipheriv("aes-256-gcm", key, iv); | |
| if (aad) { | |
| cipher.setAAD(Buffer.from(aad, "utf8")); | |
| } | |
| const ciphertext = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]); | |
| const tag = cipher.getAuthTag(); | |
| return `${toBase64Url(iv)}.${toBase64Url(ciphertext)}.${toBase64Url(tag)}`; | |
| } | |
| function decryptText(payload, key, aad = "") { | |
| const [ivPart, ciphertextPart, tagPart] = String(payload).split("."); | |
| if (!ivPart || !ciphertextPart || !tagPart) { | |
| throw new Error("Invalid encrypted payload."); | |
| } | |
| const decipher = crypto.createDecipheriv("aes-256-gcm", key, fromBase64Url(ivPart)); | |
| if (aad) { | |
| decipher.setAAD(Buffer.from(aad, "utf8")); | |
| } | |
| decipher.setAuthTag(fromBase64Url(tagPart)); | |
| const plaintext = Buffer.concat([ | |
| decipher.update(fromBase64Url(ciphertextPart)), | |
| decipher.final(), | |
| ]); | |
| return plaintext.toString("utf8"); | |
| } | |
| function encryptJson(value, key, aad = "") { | |
| return encryptText(JSON.stringify(value), key, aad); | |
| } | |
| function decryptJson(payload, key, aad = "") { | |
| return JSON.parse(decryptText(payload, key, aad)); | |
| } | |
| function hashSessionSecret(secret, salt = crypto.randomBytes(16)) { | |
| const hash = crypto.scryptSync(secret, salt, 64); | |
| return { | |
| salt: toBase64Url(salt), | |
| hash: toBase64Url(hash), | |
| }; | |
| } | |
| function verifySessionSecret(secret, expectedHash, salt) { | |
| const actual = crypto.scryptSync(secret, fromBase64Url(salt), 64); | |
| const expected = fromBase64Url(expectedHash); | |
| if (actual.length !== expected.length) { | |
| return false; | |
| } | |
| return crypto.timingSafeEqual(actual, expected); | |
| } | |
| function generateToken(size = 32) { | |
| return toBase64Url(crypto.randomBytes(size)); | |
| } | |
| function createCsrfToken(masterKey, sessionId, sessionSecret) { | |
| return crypto | |
| .createHmac("sha256", masterKey) | |
| .update(`csrf:${sessionId}:${sessionSecret}`, "utf8") | |
| .digest("base64url"); | |
| } | |
| module.exports = { | |
| createCsrfToken, | |
| decryptJson, | |
| decryptText, | |
| deriveMasterKey, | |
| deriveTokenKey, | |
| encryptJson, | |
| encryptText, | |
| generateToken, | |
| hashSessionSecret, | |
| verifySessionSecret, | |
| }; | |