UNPKG

adba

Version:
185 lines (184 loc) 6.65 kB
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()); }); }; import crypto from 'crypto'; import jwt from "jsonwebtoken"; import bcrypt from 'bcrypt'; import useragent from '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); */ export function encrypt(text, password, ivString) { const iv = ivString ? Buffer.from(ivString, 'hex') : crypto.randomBytes(16); const key = crypto.scryptSync(password, 'salt', 32); const cipher = crypto.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. */ export function decrypt(encryptedData, password, ivString) { const key = crypto.scryptSync(password, 'salt', 32); const decipher = crypto.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" */ export function generateCode() { const bytes = crypto.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.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'); */ export const buildToken = (data, pass, ivString, expiresIn) => { // Encrypt the JSON content const key_in_bytes = generateKeyFromInput(pass); const cipher = crypto.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 = jwt.sign({ payload: encrypted }, pass, { expiresIn }); // Make the token URL-safe return encodeURIComponent(token); }; /** * 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. */ export 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 = jwt.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.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; } }; /** * 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'); */ export function generatePasswordHash(password) { return __awaiter(this, void 0, void 0, function* () { try { const saltRounds = 10; const hash = yield bcrypt.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. */ export function verifyPasswordHash(password, hash) { return __awaiter(this, void 0, void 0, function* () { try { const isMatch = yield bcrypt.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'). */ export function getClientType(userAgent, device) { const agent = useragent.parse(userAgent); return agent.family; } export default { buildToken, readToken, generatePasswordHash, verifyPasswordHash, getClientType };