UNPKG

one-time-pass

Version:

Zero dependencies Node/Deno/Bun/Browser TOTP and HOTP generator based on RFC 6238 and RFC 4226

2 lines (1 loc) 4.09 kB
(function(a,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(a=typeof globalThis<"u"?globalThis:a||self,g(a.otp={}))})(this,(function(a){"use strict";const g="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function h(e){if(!e||typeof e!="string")throw new Error("Secret key must be a non-empty string");const n=e.trim().replace(/\s+/g,"").replace(/=/g,"").toUpperCase();if(n.length<1)throw new Error("Secret key must be a non-empty string");if(!/^[A-Z2-7]+$/.test(n))throw new Error("Invalid Base32 secret key.");const t=n.length*5>>>3,r=new Uint8Array(t);let o=0,i=0,s=0;for(let u=0;u<n.length;u++){const d=g.indexOf(n[u]);if(d===-1)throw new Error(`Invalid Base32 character: '${n[u]}'`);i=i<<5|d,o+=5,o>=8&&(r[s++]=i>>>o-8&255,o-=8)}return r}function m(e){if(!e||e.length===0)return"";const n=[];let t=0,r=0;for(let s=0;s<e.length;s++)for(r=r<<8|e[s],t+=8;t>=5;)n.push(g[r>>>t-5&31]),t-=5;t>0&&n.push(g[r<<5-t&31]);const o=n.join(""),i=(8-o.length%8)%8;return o+"=".repeat(i)}function p(e,n){let t=e.length===n.length?0:1;const r=Math.max(e.length,n.length);for(let o=0;o<r;o++)t|=(e.charCodeAt(o)||0)^(n.charCodeAt(o)||0);return t===0}const b=15,y=2147483647;function E(e,n){if(!Number.isInteger(n)||n<1||n>10)throw new Error("Digits must be a positive integer between 1 and 10.");if(e.length<4)throw new Error("HMAC result is too short");const t=e[e.length-1]&b;if(t>e.length-4)throw new Error("Calculated offset is out of bounds for the HMAC result");let r;try{r=new DataView(e.buffer,e.byteOffset+t,4).getUint32(0)&y}catch(i){throw new Error("Failed to read truncated code: "+i.message)}return(r%10**n).toString().padStart(n,"0")}async function f(e,n,t){const r=h(e),o=await(await l()).subtle.importKey("raw",r,{name:"HMAC",hash:{name:t.algorithm}},!1,["sign"]),i=new Uint8Array(8);new DataView(i.buffer).setBigUint64(0,BigInt(n),!1);const u=await(await l()).subtle.sign("HMAC",o,i);return E(new Uint8Array(u),t.digits)}let c=null;async function l(){var e;if(c)return c;if(typeof globalThis<"u"&&((e=globalThis.crypto)!=null&&e.subtle))return c=globalThis.crypto,c;try{const{webcrypto:n}=await import("node:crypto");if(n!=null&&n.subtle)return c=n,c}catch{}throw new Error("Web Crypto API (subtle) is not available in this environment")}async function w(e,n){if(!e||typeof e!="string")throw new Error("Secret key must be a non-empty string");const t={algorithm:"SHA-1",digits:6,counter:0,...n};if(typeof t.counter!="number"||t.counter<0||t.counter%1!==0)throw new Error("Counter must be a non-negative integer (not a float).");if(!Number.isInteger(t.digits)||t.digits<1||t.digits>10)throw new Error("Digits must be a positive integer between 1 and 10.");return f(e,t.counter,{digits:t.digits,algorithm:t.algorithm})}async function A(e=160){if(!Number.isInteger(e)||e<1||e>1024)throw new Error("Length must be a positive integer between 1 and 1024");const n=await l(),t=new Uint8Array(e);return n.getRandomValues(t),m(t)}function T(e,n={}){if(!e||typeof e!="string")throw new Error("Secret key must be a non-empty string");const t={algorithm:"SHA-1",period:30,digits:6,epoch:Date.now(),...n};if(!Number.isInteger(t.period)||t.period<=0)throw new Error("Period must be a positive integer.");const r=Math.floor(t.epoch/1e3/t.period);if(r>Number.MAX_SAFE_INTEGER||r<0)throw new Error("Counter value exceeds safe integer range");return w(e,{counter:r,algorithm:t.algorithm,digits:t.digits})}async function v(e,n,t={}){if(typeof e!="string"||!/^\d+$/.test(e))return null;const r={algorithm:"SHA-1",period:30,digits:6,epoch:Date.now(),window:1,...t};if(!Number.isInteger(r.window)||r.window<0||r.window>10)throw new Error("Window must be a non-negative integer (recommended: 0-2).");if(e.length!==r.digits)return null;const o=Math.floor(r.epoch/1e3/r.period);for(let i=-r.window;i<=r.window;i++){const s=o+i,u=await f(n,s,{digits:r.digits,algorithm:r.algorithm});if(p(u,e))return i===0?0:i}return null}a.generateHOTP=w,a.generateSecret=A,a.generateTOTP=T,a.validate=v,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));