UNPKG

otp-io

Version:

🕖 Typed library to work 2fa via Google Authenticator/Time-based TOTP/Hmac-based HOTP

108 lines • 3.48 kB
const charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; const byteTable = new Uint8Array([ 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff ]); /** * * * @param {Uint8Array} buffer * @return {number} */ function quintetCount(buffer) { return Math.ceil(buffer.length / 5); } /** * * * @export * @param {Uint8Array} plain * @return {string} {string} */ function encode(plain, { pad = true } = {}) { let index = 0; let encodedIndex = 0; let shiftIndex = 0; let digit = 0; const encoded = Array.from({ length: quintetCount(plain) * 8 }); while (index < plain.length) { const current = plain[+index]; if (shiftIndex > 3) { digit = current & (0xff >> shiftIndex); shiftIndex = (shiftIndex + 5) % 8; digit = (digit << shiftIndex) | ((index + 1 < plain.length ? plain[index + 1] : 0) >> (8 - shiftIndex)); index++; } else { digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; shiftIndex = (shiftIndex + 5) % 8; if (shiftIndex === 0) index++; } encoded[+encodedIndex] = charTable.codePointAt(digit); encodedIndex++; } if (pad) { for (index = encodedIndex; index < encoded.length; index++) { encoded[+index] = 0x3d; } } else { encoded.splice(encodedIndex); } return String.fromCharCode(...encoded); } /** * * * @export * @param {string} encoded * @return {Uint8Array} {Uint8Array} */ function decode(encoded) { let shiftIndex = 0; let plainDigit = 0; let plainChar = 0; let plainPos = 0; const decoded = new Uint8Array(Math.ceil((encoded.length * 5) / 8)); const chars = encoded.split("").map((char) => char.charCodeAt(0)); for (let index = 0; index < encoded.length; index++) { if (chars[+index] === 0x3d) { break; } const encodedByte = chars[+index] - 0x30; if (encodedByte < byteTable.length) { plainDigit = byteTable[+encodedByte]; if (shiftIndex <= 3) { shiftIndex = (shiftIndex + 5) % 8; if (shiftIndex === 0) { plainChar |= plainDigit; decoded[+plainPos] = plainChar; plainPos++; plainChar = 0; } else { plainChar |= 0xff & (plainDigit << (8 - shiftIndex)); } } else { shiftIndex = (shiftIndex + 5) % 8; plainChar |= 0xff & (plainDigit >>> shiftIndex); decoded[+plainPos] = plainChar; plainPos++; plainChar = 0xff & (plainDigit << (8 - shiftIndex)); } } else { throw new Error("Invalid input - it is not base32 encoded string"); } } return decoded.slice(0, plainPos); }export{decode,encode};