@alessiofrittoli/crypto-otp
Version:
Lightweight TypeScript HOTP/TOTP library
2 lines (1 loc) • 3.44 kB
JavaScript
import D from"crypto";import{Url as x}from"@alessiofrittoli/url-utils";import{Base32 as l}from"@alessiofrittoli/crypto-encoder/Base32";import{Hmac as d,generateKey as h}from"@alessiofrittoli/crypto-key";import{padStart as G}from"@alessiofrittoli/math-utils/helpers";var i=class i{static DigestToToken(t,e=i.Digits){let r=(t.at(-1)??0)&15,n=(((t[r]??0)&127)<<24|((t[r+1]??0)&255)<<16|((t[r+2]??0)&255)<<8|(t[r+3]??0)&255)%Math.pow(10,e);return i.padStart(String(n),e,"0")}static HmacKey(t,e){return e!=="base32"?Buffer.from(t,e).toString("hex"):Buffer.from(l.decode(t,i.Base32Variant)).toString("hex")}static createDigest(t,e,r){return d.digest(Buffer.from(r,"hex"),Buffer.from(e,"hex"),t)}static Seed(t){return d.digest(t||h(4),h(),"SHA-1","hex").toUpperCase()}static GenerateSecretASCII(t=40,e=!1){let r=D.randomBytes(t),o="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"+(e?"!@#$%^&*()<>?/[]{},.:;":""),n="";for(let a=0,u=r.length;a<u;a++)n+=o[Math.floor(r[a]/255*(o.length-1))];return n}static GetAuthURL(t){let{secret:{algorithm:e=i.Algorithm,encoding:r=i.Encoding},digits:o=i.Digits,type:n,label:a,issuer:u}=t,{key:m}=t.secret,O=e.replace(/-/g,"").toUpperCase();r!=="base32"&&(m=l.encode(Buffer.from(m,r),i.Base32Variant));let c={secret:m.toString(),algorithm:O,digits:o};return u&&(c.issuer=u),n==="hotp"&&(c.counter=t.counter),n==="totp"&&t.period&&(c.period=t.period),x.format({protocol:"otpauth",hostname:n,pathname:encodeURIComponent(a),query:c})}static GetSecrets(t){let{secret:{encoding:e=i.Encoding,key:r}}=t;return Object.fromEntries(["ascii","hex","base64url","base32"].map(o=>o===e?[o,r]:o==="base32"?[o,l.encode(Buffer.from(r,e),i.Base32Variant)]:e==="base32"?[o,Buffer.from(l.decode(r,i.Base32Variant)).toString(o)]:[o,Buffer.from(r,e).toString(o)]))}};i.Digits=6,i.Encoding="hex",i.Algorithm="SHA-1",i.Base32Variant=l.VARIANT.RFC3548,i.padStart=G;var p=i;import{timingSafeEqual as b}from"crypto";import{Exception as B}from"@alessiofrittoli/exception";import{ErrorCode as A}from"@alessiofrittoli/exception/code";var T=class s extends p{static Verify(t){return s.GetDelta(t)!=null}static GetDelta(t,e=!1){let{token:r}=t;if(!r)throw new B("No token has been provided.",{code:A.EMPTY_VALUE});let{counter:o=0,window:n=0,digits:a=s.Digits,...u}=t,m=e?o-n:o,O=e?n*2:n;if(r.length!==a)return null;for(let c=m;c<=m+O;++c){let y=s.GetToken({...u,digits:a,counter:c});if(b(Buffer.from(y),Buffer.from(r))){let g=c-m;return e?g-n:g}}return null}static GetToken(t){let{digits:e=s.Digits}=t;return s.DigestToToken(s.Digest(t),e)}static Digest(t){let{counter:e=0,secret:{key:r,algorithm:o=s.Algorithm,encoding:n=s.Encoding}}=t;return s.createDigest(o,s.HmacKey(r,n),s.Counter(e))}static Counter(t){return s.padStart(t.toString(16),16,"0")}static AuthURL(t){let{counter:e=0,...r}=t;return p.GetAuthURL({counter:e,...r,type:"hotp"})}};var f=class f extends p{static Verify(t){return f.GetDelta(t)!=null}static GetDelta(t){return T.GetDelta({...t,counter:f.Counter(t)},!0)}static GetToken(t){return T.GetToken({...t,counter:f.Counter(t)})}static Counter(t={}){let{period:e=f.Period,time:r=Date.now()/1e3,epoch:o=0}=t,n=r*1e3,a=o*1e3;return Math.floor((n-a)/e/1e3)}static NextTick(t={}){let{period:e=f.Period,epoch:r=0,...o}=t,n=r*1e3,a=f.Counter({period:e,epoch:r,...o}),u=n+(a+1)*e*1e3;return new Date(u)}static AuthURL(t){return p.GetAuthURL({...t,type:"totp"})}};f.Period=30;var P=f;export{T as Hotp,p as Otp,P as Totp};