UNPKG

adba

Version:
199 lines (198 loc) 7.46 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.readToken = exports.buildToken = void 0; exports.encrypt = encrypt; exports.decrypt = decrypt; exports.generateCode = generateCode; exports.generatePasswordHash = generatePasswordHash; exports.verifyPasswordHash = verifyPasswordHash; exports.getClientType = getClientType; const crypto_1 = __importDefault(require("crypto")); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const bcrypt_1 = __importDefault(require("bcrypt")); const useragent_1 = __importDefault(require("useragent")); /** * Encrypt a string using AES-256-CBC. * * @param text - Plain text to encrypt. * @param password - Password used for key derivation. * @param ivString - Initialization vector in hex format. * @returns Object containing encrypted data and IV. * * @example * const { encryptedData, iv } = encrypt('hello', 'pass', ''); * const plain = decrypt(encryptedData, 'pass', iv); */ function encrypt(text, password, ivString) { const iv = ivString ? Buffer.from(ivString, 'hex') : crypto_1.default.randomBytes(16); const key = crypto_1.default.scryptSync(password, 'salt', 32); const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return { encryptedData: encrypted, iv: iv.toString('hex') }; } /** * Decrypt data encrypted with {@link encrypt}. * * @param encryptedData - The hexadecimal encrypted string. * @param password - Password used for key derivation. * @param ivString - Initialization vector in hex format. * @returns The decrypted plain text. */ function decrypt(encryptedData, password, ivString) { const key = crypto_1.default.scryptSync(password, 'salt', 32); const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', key, Buffer.from(ivString, 'hex')); let decrypted = decipher.update(encryptedData, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } /** * Generates a unique 6-digit code. * * @returns A 6-digit code as string. * * @example * const code = generateCode(); * console.log(code); // "482901" */ function generateCode() { const bytes = crypto_1.default.randomBytes(4); const uint = bytes.readUInt32BE(0); const sixDigitCode = uint % 1000000; return sixDigitCode.toString().padStart(6, '0'); } /** * Generates a 32-byte key from any given string. * @param {string} input - The input string from which to generate the key. * @returns {Buffer} A 32-byte Buffer containing the key. */ const generateKeyFromInput = (input) => { // Create a SHA-256 hash of the input const hash = crypto_1.default.createHash('sha256'); hash.update(input); return hash.digest(); // This returns a Buffer with the 32-byte hash }; /** * Build a secure token that contains encrypted data. * * @param data - Payload to include in the token. * @param pass - Encryption key. * @param ivString - Initialization vector in hex format. * @param expiresIn - Expiration time for the JWT. * @returns URL-safe encrypted token string. * * @example * const token = buildToken({ payload: { foo: 'bar' } }, 'pass', iv, '1h'); */ const buildToken = (data, pass, ivString, expiresIn) => { // Encrypt the JSON content const key_in_bytes = generateKeyFromInput(pass); const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key_in_bytes, Buffer.from(ivString, 'hex')); let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'base64'); encrypted += cipher.final('base64'); // Generate the JWT token const token = jsonwebtoken_1.default.sign({ payload: encrypted }, pass, { expiresIn }); // Make the token URL-safe return encodeURIComponent(token); }; exports.buildToken = buildToken; /** * Decrypt a token created with {@link buildToken}. * * @param encryptedToken - The encrypted token. * @param pass - Decryption key. * @param ivString - Initialization vector in hex format. * @returns The decrypted payload or an Error instance. */ const readToken = (encryptedToken, pass, ivString) => { try { // Decode the token first if it is URL-encoded const token = decodeURIComponent(encryptedToken); // Verify and decode the JWT token const decoded = jsonwebtoken_1.default.verify(token, pass); if (typeof decoded !== 'object') { throw new Error("decoded data is not an object, spect an object:\n" + decoded); } // Decrypt the payload const key_in_bytes = generateKeyFromInput(pass); const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', key_in_bytes, Buffer.from(ivString, 'hex')); let decrypted = decipher.update(decoded.payload, 'base64', 'utf8'); decrypted += decipher.final('utf8'); // Parse the decrypted content to an object const parsed = JSON.parse(decrypted); return parsed; } catch (err) { return err; } }; exports.readToken = readToken; /** * Generate a bcrypt hash from a plain password. * * @param password - Password to hash. * @returns The hashed password or an Error. * * @example * const hash = await generatePasswordHash('secret'); */ function generatePasswordHash(password) { return __awaiter(this, void 0, void 0, function* () { try { const saltRounds = 10; const hash = yield bcrypt_1.default.hash(password, saltRounds); return hash; } catch (error) { return error; } }); } /** * Verify a password against a bcrypt hash. * * @param password - Plain password to verify. * @param hash - Previously generated hash. * @returns True if the password matches, otherwise false or an Error. */ function verifyPasswordHash(password, hash) { return __awaiter(this, void 0, void 0, function* () { try { const isMatch = yield bcrypt_1.default.compare(password, hash); return isMatch; } catch (error) { return error; } }); } /** * Determine the client family from a user-agent string. * * @param userAgent - Raw user-agent header string. * @param device - Optional device hint. * @returns The detected client family (e.g. 'Chrome'). */ function getClientType(userAgent, device) { const agent = useragent_1.default.parse(userAgent); return agent.family; } exports.default = { buildToken: exports.buildToken, readToken: exports.readToken, generatePasswordHash, verifyPasswordHash, getClientType };