summaryrefslogtreecommitdiff
path: root/app/lib/otp.js
blob: 14763a8d18a24fa53299ac0d496b4a584d44f498 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { createHmac } from "node:crypto";
const getHMAC = (k,c) => { const h = createHmac("sha1", k);
  h.update(c, "hex"); return h.digest("hex"); }

const h2d = s => parseInt(s, 16);
const d2h = s => Math.floor(s).toString(16).padStart(2,0);

const b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const b32u = s => Uint8Array.from(s.split("").reduce((a,c) =>
    a+b32a.indexOf(c.toUpperCase()).toString(2).padStart(5,0), "")
  .match(/.{8}/g).map(b => parseInt(b,2)));

export default function totp(secret, { expiry=30,
  now=Math.round(new Date().getTime()/1000), length=6 }={}) {
  const time = d2h(now/expiry).padStart(16,0);
  const hmac = getHMAC(b32u(secret), time);
  const offset = h2d(hmac.substring(hmac.length - 1));
  const otp = (h2d(hmac.substr(2*offset, 8)) & h2d("7fffffff")) + "";
  return otp.padStart(length,0).substr(-length);
}

export function check(token, secret, { expiry=30,
  now=Math.round(new Date().getTime()/1000), length=6, window=0 }={}) {
  const i = Array(window*2+1).fill().map((e,i) => now+(expiry*(i-window)))
    .findIndex(n => token === this(secret, {now:n, expiry, length}));
  return i>=0 && i-window;
}