otpauth
Version:
One Time Password (HOTP/TOTP) library for Node.js and browsers
347 lines (285 loc) • 7.21 kB
JavaScript
/**
* An object containing some utilities.
* @type {Object}
*/
export const Utils = {
/**
* UInt conversion.
* @type {Object}
*/
uint: {
/**
* Converts an ArrayBuffer to an integer.
* @param {ArrayBuffer} buf ArrayBuffer.
* @returns {number} Integer.
*/
fromBuf: buf => {
const arr = new Uint8Array(buf);
let num = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) {
num *= 256;
num += arr[i];
}
}
return num;
},
/**
* Converts an integer to an ArrayBuffer.
* @param {number} num Integer.
* @returns {ArrayBuffer} ArrayBuffer.
*/
toBuf: num => {
const buf = new ArrayBuffer(8);
const arr = new Uint8Array(buf);
let acc = num;
for (let i = 7; i >= 0; i--) {
if (acc === 0) break;
arr[i] = acc & 255;
acc -= arr[i];
acc /= 256;
}
return buf;
}
},
/**
* Raw string conversion.
* @type {Object}
*/
raw: {
/**
* Converts an ArrayBuffer to a string.
* @param {ArrayBuffer} buf ArrayBuffer.
* @returns {string} String.
*/
fromBuf: buf => {
const arr = new Uint8Array(buf);
let str = '';
for (let i = 0; i < arr.length; i++) {
str += String.fromCharCode(arr[i]);
}
return str;
},
/**
* Converts a string to an ArrayBuffer.
* @param {string} str String.
* @returns {ArrayBuffer} ArrayBuffer.
*/
toBuf: str => {
const buf = new ArrayBuffer(str.length);
const arr = new Uint8Array(buf);
for (let i = 0; i < str.length; i++) {
arr[i] = str.charCodeAt(i);
}
return buf;
}
},
/**
* Base32 string conversion.
* @type {Object}
*/
b32: {
/**
* RFC 4648 base32 alphabet without pad.
* @type {string}
*/
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
/**
* Converts an ArrayBuffer to a base32 string (RFC 4648)
* (https://github.com/LinusU/base32-encode).
* @param {ArrayBuffer} buf ArrayBuffer.
* @returns {string} Base32 string.
*/
fromBuf: buf => {
const arr = new Uint8Array(buf);
let bits = 0;
let value = 0;
let str = '';
for (let i = 0; i < arr.length; i++) {
value = (value << 8) | arr[i];
bits += 8;
while (bits >= 5) {
str += Utils.b32.alphabet[(value >>> bits - 5) & 31];
bits -= 5;
}
}
if (bits > 0) {
str += Utils.b32.alphabet[(value << 5 - bits) & 31];
}
return str;
},
/**
* Converts a base32 string to an ArrayBuffer (RFC 4648)
* (https://github.com/LinusU/base32-decode).
* @param {string} str Base32 string.
* @returns {ArrayBuffer} ArrayBuffer.
*/
toBuf: str => {
// Canonicalize to all upper case and remove padding if it exists.
str = str.toUpperCase().replace(/=+$/, '');
const buf = new ArrayBuffer((str.length * 5) / 8 | 0);
const arr = new Uint8Array(buf);
let bits = 0;
let value = 0;
let index = 0;
for (let i = 0; i < str.length; i++) {
const idx = Utils.b32.alphabet.indexOf(str[i]);
if (idx === -1) throw new TypeError(`Invalid character found: ${str[i]}`);
value = (value << 5) | idx;
bits += 5;
if (bits >= 8) {
arr[index++] = (value >>> bits - 8) & 255;
bits -= 8;
}
}
return buf;
}
},
/**
* Hexadecimal string conversion.
* @type {Object}
*/
hex: {
/**
* Converts an ArrayBuffer to a hexadecimal string.
* @param {ArrayBuffer} buf ArrayBuffer.
* @returns {string} Hexadecimal string.
*/
fromBuf: buf => {
const arr = new Uint8Array(buf);
let str = '';
for (let i = 0; i < arr.length; i++) {
const hex = arr[i].toString(16);
str += hex.length === 2 ? hex : `0${hex}`;
}
return str.toUpperCase();
},
/**
* Converts a hexadecimal string to an ArrayBuffer.
* @param {string} str Hexadecimal string.
* @returns {ArrayBuffer} ArrayBuffer.
*/
toBuf: str => {
const buf = new ArrayBuffer(str.length / 2);
const arr = new Uint8Array(buf);
for (let i = 0, j = 0; i < arr.length; i += 1, j += 2) {
arr[i] = parseInt(str.substr(j, 2), 16);
}
return buf;
}
},
/**
* Pads a number with leading zeros.
* @param {number|string} num Number.
* @param {number} digits Digits.
* @returns {string} Padded number.
*/
pad: (num, digits) => {
let prefix = '';
let repeat = digits - String(num).length;
while (repeat-- > 0) prefix += '0';
return `${prefix}${num}`;
}
};
/**
* An object containing some utilities (for internal use only).
* @private
* @type {Object}
*/
export const InternalUtils = {
/**
* "globalThis" ponyfill
* (https://mathiasbynens.be/notes/globalthis).
* @type {Object}
*/
get globalThis() {
let _globalThis;
/* eslint-disable no-extend-native, no-restricted-globals, no-undef */
if (typeof globalThis === 'object') {
_globalThis = globalThis;
} else {
Object.defineProperty(Object.prototype, '__magicalGlobalThis__', {
get() { return this; },
configurable: true
});
try {
_globalThis = __magicalGlobalThis__;
} finally {
delete Object.prototype.__magicalGlobalThis__;
}
}
if (typeof _globalThis === 'undefined') {
// Still unable to determine "globalThis", fall back to a naive method.
if (typeof self !== 'undefined') {
_globalThis = self;
} else if (typeof window !== 'undefined') {
_globalThis = window;
} else if (typeof global !== 'undefined') {
_globalThis = global;
}
}
/* eslint-enable */
Object.defineProperty(this, 'globalThis', {
enumerable: true,
value: _globalThis
});
return this.globalThis;
},
/**
* "console" ponyfill.
* @type {Object}
*/
get console() {
const _console = {};
const methods = [
'assert', 'clear', 'context', 'count', 'countReset', 'debug', 'dir', 'dirxml',
'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'profile',
'profileEnd', 'table', 'time', 'timeEnd', 'timeLog', 'timeStamp', 'trace', 'warn'
];
if (typeof InternalUtils.globalThis.console === 'object') {
for (const method of methods) {
_console[method] = typeof InternalUtils.globalThis.console[method] === 'function'
? InternalUtils.globalThis.console[method]
: () => {};
}
} else {
for (const method of methods) {
_console[method] = () => {};
}
}
Object.defineProperty(this, 'console', {
enumerable: true,
value: _console
});
return this.console;
},
/**
* Detect if running in "Node.js".
* @type {boolean}
*/
get isNode() {
const _isNode = Object.prototype.toString.call(InternalUtils.globalThis.process) === '[object process]';
Object.defineProperty(this, 'isNode', {
enumerable: true,
value: _isNode
});
return this.isNode;
},
/**
* Dynamically import "Node.js" modules.
* (`eval` is used to prevent bundlers from including the module,
* e.g., [webpack/webpack#8826](https://github.com/webpack/webpack/issues/8826))
* @type {Function}
*/
get nodeRequire() {
const _nodeRequire = InternalUtils.isNode
// eslint-disable-next-line no-eval
? eval('require')
: () => {};
Object.defineProperty(this, 'nodeRequire', {
enumerable: true,
value: _nodeRequire
});
return this.nodeRequire;
}
};