Add missing modules
This commit is contained in:
parent
74c3baff53
commit
96613afb23
|
@ -0,0 +1,52 @@
|
|||
import { Err, ErrorCode } from "./error";
|
||||
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
export function bytesToBase32(arr: Uint8Array) {
|
||||
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 += chars[(value >>> (bits - 5)) & 31];
|
||||
bits -= 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (bits > 0) {
|
||||
str += chars[(value << (5 - bits)) & 31];
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function base32ToBytes(str: string) {
|
||||
const strUpp = str.toUpperCase();
|
||||
const arr = new Uint8Array(((str.length * 5) / 8) | 0);
|
||||
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
let index = 0;
|
||||
|
||||
for (let i = 0; i < strUpp.length; i++) {
|
||||
const idx = chars.indexOf(strUpp[i]);
|
||||
|
||||
if (idx === -1) {
|
||||
throw new Err(ErrorCode.ENCODING_ERROR, `Invalid Base32 character found: ${strUpp[i]}`);
|
||||
}
|
||||
|
||||
value = (value << 5) | idx;
|
||||
bits += 5;
|
||||
|
||||
if (bits >= 8) {
|
||||
arr[index++] = (value >>> (bits - 8)) & 255;
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { numToBytes, bytesToNum } from "./encoding";
|
||||
import { getProvider, HMACParams } from "./crypto";
|
||||
import { base32ToBytes } from "./encoding";
|
||||
|
||||
export interface HOTPOpts {
|
||||
digits: number;
|
||||
hash: "SHA-1" | "SHA-256";
|
||||
}
|
||||
|
||||
export interface TOTPOpts extends HOTPOpts {
|
||||
interval: number;
|
||||
}
|
||||
|
||||
function getToken(hmac: Uint8Array, digits: number = 6): string {
|
||||
const offset = hmac[hmac.length - 1] & 0xf;
|
||||
const bin = new Uint8Array([hmac[offset] & 0x7f, hmac[offset + 1], hmac[offset + 2], hmac[offset + 3]]);
|
||||
const num = bytesToNum(bin);
|
||||
return (num % 10 ** digits).toString().padStart(digits, "0");
|
||||
}
|
||||
|
||||
export async function hotp(
|
||||
secret: Uint8Array,
|
||||
counter: number,
|
||||
{ hash, digits }: HOTPOpts = { digits: 6, hash: "SHA-1" }
|
||||
) {
|
||||
const hmac = await getProvider().sign(
|
||||
secret,
|
||||
numToBytes(counter),
|
||||
new HMACParams({ hash, keySize: secret.length * 8 })
|
||||
);
|
||||
return getToken(hmac, digits);
|
||||
}
|
||||
|
||||
export async function totp(
|
||||
secret: Uint8Array,
|
||||
time: number = Date.now(),
|
||||
{ interval, ...opts }: TOTPOpts = { interval: 30, digits: 6, hash: "SHA-1" }
|
||||
) {
|
||||
const counter = Math.floor(time / interval / 1000);
|
||||
return hotp(secret, counter, opts);
|
||||
}
|
||||
|
||||
export function parseURL(data: string) {
|
||||
const url = new URL(data);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const secret = params.get("secret");
|
||||
|
||||
if (!secret || !base32ToBytes(secret).length) {
|
||||
throw "Invalid secret";
|
||||
}
|
||||
|
||||
return { secret };
|
||||
}
|
Loading…
Reference in New Issue