Giter Site home page Giter Site logo

hectorm / otpauth Goto Github PK

View Code? Open in Web Editor NEW
829.0 10.0 51.0 7.69 MB

One Time Password (HOTP/TOTP) library for Node.js, Deno, Bun and browsers.

Home Page: https://hectorm.github.io/otpauth/

License: MIT License

JavaScript 98.89% HTML 1.11%
otpauth hotp totp otp two-factor two-step-authentication two-factor-authentication two-step authenticator google-authenticator

otpauth's Introduction

Last version npm downloads

OTPAuth

One Time Password (HOTP/TOTP) library for Node.js, Deno, Bun and browsers.

Usage

Node.js

import * as OTPAuth from "otpauth";

// Create a new TOTP object.
let totp = new OTPAuth.TOTP({
  issuer: "ACME",
  label: "AzureDiamond",
  algorithm: "SHA1",
  digits: 6,
  period: 30,
  secret: "NB2W45DFOIZA", // or 'OTPAuth.Secret.fromBase32("NB2W45DFOIZA")'
});

// Generate a token (returns the current token as a string).
let token = totp.generate();

// Validate a token (returns the token delta or null if it is not found in the search window, in which case it should be considered invalid).
let delta = totp.validate({ token, window: 1 });

// Convert to Google Authenticator key URI:
// otpauth://totp/ACME:AzureDiamond?issuer=ACME&secret=NB2W45DFOIZA&algorithm=SHA1&digits=6&period=30
let uri = totp.toString(); // or 'OTPAuth.URI.stringify(totp)'

// Convert from Google Authenticator key URI.
totp = OTPAuth.URI.parse(uri);

Deno

import * as OTPAuth from "https://deno.land/x/otpauth@VERSION/dist/otpauth.esm.js";

// Same as above.

Bun

import * as OTPAuth from "otpauth";

// Same as above.

Browsers

<script src="https://cdnjs.cloudflare.com/ajax/libs/otpauth/VERSION/otpauth.umd.min.js"></script>
<script>
  // Same as above.
</script>

Documentation

See the documentation page.

https://hectorm.github.io/otpauth/

Supported hashing algorithms

In Node.js, the same algorithms as Crypto.createHmac function are supported, since it is used internally. In Deno, Bun and browsers, the SHA1, SHA224, SHA256, SHA384, SHA512, SHA3-224, SHA3-256, SHA3-384 and SHA3-512 algorithms are supported by using the jsSHA library.

License

MIT License © Héctor Molinero Fernández.

otpauth's People

Contributors

air2 avatar dependabot[bot] avatar hectorm avatar perry-mitchell avatar wpf500 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

otpauth's Issues

Adding image to the totp object

I am trying to integrate our company's logo into the OTPAuth TOTP object for better branding and visual identification. Currently, the object is initialized as follows:

const totp = new OTPAuth.TOTP({
   issuer: "Company name",
   label: "label",
   algorithm: "SHA1",
   digits: 6,
   period: 30,
   secret: OTPAuth.Secret.fromBase32(secret),
});

I would like to know the process or method to include our company logo alongside the issuer name in the TOTP object. Any guidance or suggestions on achieving this would be greatly appreciated. Thank you!

Invalid character found: @

let totp = new TOTP({
issuer: 'Y',
label: 'Y',
algorithm: 'SHA512',
digits: 10,
period: 30,
secret: '[email protected]'
});
let myTotp = totp.generate();

call stack:
if (idx === -1) throw new TypeError("Invalid character found: ".concat(str[i]));
^

TypeError: Invalid character found: @
at Object.toBuf (F:\yogi\hieng\files\node_modules\otpauth\dist\otpauth.cjs.js:265:31)
at Function.fromB32 (F:\yogi\hieng\files\node_modules\otpauth\dist\otpauth.cjs.js:2083:27)
at new TOTP (F:\yogi\hieng\files\node_modules\otpauth\dist\otpauth.cjs.js:2354:55)
at Object. (F:\yogi\hieng\files\main.js:26:12)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)

Time sync token

Hello,
I'm currently developing a 2fa authenticator application based on the web platform. May I know how to utilize time synchronization with the token generated by the library?

Here is the application developed by myself: Otentikator

Thank you!

getting null when validate with token can any oine help me to find what exactly issue

 generateRandomBase32 = () => {
    const buffer = crypto.randomBytes(15);
    const base32 = encode(buffer).replace(/=/g, '').substring(0, 24);
    return base32;
  };

  totpGenerate(secret: string): OTPAuth.TOTP {
    const totp = new OTPAuth.TOTP({
      issuer: Config.ISSUER,
      label: Config.LABEL,
      algorithm: Config.ALGORITHM,
      digits: Number(Config.DIGIT),
      period: Number(Config.PERIOD),
      secret: secret,
    });
    return totp;
  }
async enable2FA(towFAEnableDto: TowFAEnableDto) {
    try {
      const user = await this.userRepository.find({
        uuid: towFAEnableDto.userUuid,
      });

      if (!user) {
        throw new NotFoundException('User Not found');
      }
      const secret = this.generateRandomBase32();

      /**
       * TOTP: Time-based One-time Password
       */
      const totp = this.totpGenerate(secret);
      const url = totp.toString();
      const payload = await this.towFARepository.createOrUpdate(user.uuid, {
        appAuthUrl: url,
        secret: secret,
        enable: towFAEnableDto.enable,
        authType: towFAEnableDto.authType,
      });
      return payload;
    } catch (error) {
      throw errorHandler(error);
    }
  }

  async login2fa({ userUuid, token }: Login2faDto) {
    try {
      let user: any = await this.userRepository.find({
        uuid: userUuid,
        softDelete: false,
      });

      user = this.exclude(user, ['password']);

      if (!user) {
        throw new NotFoundException('Invalid user');
      }
      const totp = this.totpGenerate(user.towFa.secret);
      // const t = totp.generate();
      const delta = totp.validate({ token });
      if (delta === null) throw new NotFoundException('Invalid OTP');
      return true;
    } catch (error) {
      throw errorHandler(error);
    }
  }

To much code generation with a window size + 1

Currently when specifying a window size, all codes within that window are generated. I think this is done to keep the timing equal for every code checked. I think this is done against timing attacks? However, I do not see any drawbacks of an attacker seeing which timed code is accepted. In other words when 3 codes are generated (a window of size 1) and an attacker would now the code is matched against number 2, the attacker gains no real info. (in contrary to individual digits in the code, which should ALWAYS ALL be checked)

So I do not see any draw backs to generate the code for counter 0 end if it not matches for counter - 1 if it not matches for counter + 1 etc. until a match is found, this would improve the matching speed and reduce the number of calculations to be performed when checking codes (Especially with bigger window sizes). (Because mostly the time of the client will be equal, or close to equal to the server) So this will only gives a penalty when the client clock is off, or the client is providing a just expired token (and the window stills allows it, or when the client provides an non valid token) instead of calculating the codes always.

If this agreed upon I would happily provide a pull request implementing this (IMHO) optimisation. But maybe I am overlooking something, so please let me know if this somehow a bad idea.

TOTP validation not working

Goal

Trying to validate a TOTP token for a given secret.

Description

Expected behaviour:

  • correct token: alert success/delta not null
  • wrong token: alert with the correct and different token for the current time/delta is null

Observed behaviour:

  • correct token: delta is null, generated token is equal to input token
  • wrong token: delta is null, generated token as expected

The behaviour is the same when I swap the position of generation and validation.

Test

The code below is almost directly taken from the README.

<!DOCTYPE html>
<html>
  <body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/otpauth/9.1.5/otpauth.umd.min.js"></script>
  <script>
    let totp = new OTPAuth.TOTP({
    issuer: "ACME",
    label: "AzureDiamond",
    algorithm: "SHA1",
    digits: 6,
    period: 30,
    secret: "NB2W45DFOIZA",
  });

  // Validate a token (returns the token delta or null if it is not found in the search window, in which case it should be considered invalid).
  let token = prompt("TOTP: ");
  let delta = totp.validate({ token, window: 1 });
  if (delta) {
    alert("You got lucky.. this time");
  } else {
    alert("Wrong TOTP: " + totp.generate());
  }
  </script>
  </body>
</html>

Validate is broken and considers single digit tokens as valid.

So.
I found when debugging some error handling stuff that using "1" as a token was considered valid. I only had delta !== null check to see if the token was valid and the single digit token returned a negative integer so the check passed.

On further exploration I found that single digit tokens very often return integers indicating a valid token.

Here is some code to recreate it:

const OTPAuth = require("otpauth");

const totp = new OTPAuth.TOTP({
  algorithm: "SHA1",
  digits: 6,
  period: 30,
  secret: OTPAuth.Secret.fromB32("NB2W45DFOIZA")
});

const token = totp.generate();

let delta = totp.validate({window: 10, token: token});
console.log(`Valid token "${token}" has delta ${delta}`);

delta = totp.validate({window: 10, token: "420420"});
console.log(`Invalid token has delta ${delta}`);

let deltas = {};
for (let i = 0; i < 10; i++) {
  deltas[i] = totp.validate({
    window: 10,
    token: `${i}`
  });
}
console.log("Single digit tokens:", deltas);

Output:

Valid token "102040" has delta 0
Invalid token has delta null
Single digit tokens: {
  '0': 0,
  '1': 7,
  '2': -6,
  '3': -8,
  '4': -3,
  '5': null,
  '6': 2,
  '7': -2,
  '8': -9,
  '9': -10
}

Changing window, period or digits gives similar results.

Tested on otpauth v3.2.7.
Running node v12.1.0.

How to show the icon on auth app?

When I add github/npm/notion/... to auth app, it will show the icon of these service. otpauth doesn't have image or icon option, how can I set the icon?

Webpack Compilation Error

Issue

Trying to use the otpauth library in any capacity in Angular 8.2.0 will cause a webpack compilation error.

I haven't investigated too thoroughly, but, based on how Webpack interprets the distribution of OTPAuth. It doesn't register $jscomp.global.Object.defineProperties properly on line 821 of the dist file.

Steps to reproduce

Try to incorporate the code in any capacity. In the example provided. I just put the OTP functions in a service.

import { Injectable } from '@angular/core';
import { TOTP, Secret } from 'otpauth';
import { encode } from 'hi-base32';

@Injectable({ providedIn: 'root' })
export class OtpService {
  constructor() { }

  public createSecret(length: number = 36): string {
    const randomString = Math.random().toString(36);
    return encode(randomString, true).slice(0, length);
  }

  public createAuth() {
    const secretb32 = this.createSecret();
    const secret = Secret.fromRaw(secretb32);
    return secret;
  }
}

Stack Trace

otpauth.js:821 Uncaught TypeError: Cannot read property 'defineProperties' of undefined
    at Module.<anonymous> (otpauth.js:821)
    at __webpack_require__ (otpauth.js:101)
    at otpauth.js:147
    at otpauth.js:148
    at otpauth.js:93
    at Object.<anonymous> (otpauth.js:94)
    at Object../node_modules/otpauth/dist/otpauth.js (otpauth.js:1009)
    at __webpack_require__ (bootstrap:79)
    at Module../src/app/service/otp.service.ts (app.module.ts:25)
    at __webpack_require__ (bootstrap:79)
(anonymous) @ otpauth.js:821
__webpack_require__ @ otpauth.js:101
(anonymous) @ otpauth.js:147
(anonymous) @ otpauth.js:148
(anonymous) @ otpauth.js:93
(anonymous) @ otpauth.js:94
./node_modules/otpauth/dist/otpauth.js @ otpauth.js:1009
__webpack_require__ @ bootstrap:79
./src/app/service/otp.service.ts @ app.module.ts:25
__webpack_require__ @ bootstrap:79
./src/app/app.module.ts @ app.component.ts:10
__webpack_require__ @ bootstrap:79
./src/main.ts @ main.ts:1
__webpack_require__ @ bootstrap:79
0 @ main.ts:12
__webpack_require__ @ bootstrap:79
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.js:1

Problem with Vercel Edge Runtime

The Edge Runtime is a browser-like runtime (no Node APIs available), but it also explicitly disallows dynamic code execution (no eval()). Could you add a way to import the browser-safe methods directly without relying on the Node check that uses eval()?

Edge Runtime limitations

Add "browser" field to package.json

In package.json there is currently an entry "main": "./dist/otpauth.node.cjs",. There is also "exports" which contains the "default": "./dist/otpauth.esm.js".
My suggestion is to add "browser": "./dist/otpauth.esm.js", next to "main". This allows bundlers to find the correct entry points when resolving modules for the web.


My problem:
I'm trying to use otpauth in Cypress end-to-end tests. Cypress compiles test files via webpack to be run in a browser environment. However with the current configuration in package.json it throws an error:

Error: Webpack Compilation Error
./node_modules/otpauth/dist/otpauth.node.cjs
Module not found: Error: Can't resolve 'node:crypto' in '/path/to/my/project/node_modules/otpauth/dist'
resolve 'node:crypto' in '/path/to/my/project/node_modules/otpauth/dist'
  Parsed request is a module
  using description file: /path/to/my/project/node_modules/otpauth/package.json (relative path: ./dist)
    Field 'browser' doesn't contain a valid alias configuration

As you can see it is trying to import the node version rather than the esm version of the package.
Note the last line about the missing "browser" configuration. Adding the line I've suggested above fixes the compilation error.

Edit: I'm importing in a typescript file like this:

import { TOTP } from "otpauth"

If I instead import like this:

import { TOTP } from "../../node_modules/otpauth/dist/otpauth.esm.js"

it works, but I lose all type checking information as the .d.ts file can no longer be resolved.

Time left on token

Hi, is there a way to get how many seconds left before a generated token expires?

Unable to resolve module node:crypto

When I use otpauth with taro for react-native, the error occured

Unable to resolve module node:crypto from /Users/xxx/WebstormProjects/otp-fe/node_modules/otpauth/dist/otpauth.node.cjs: node:crypto could not be found within the project or in these directories:
  node_modules
  4 | Object.defineProperty(exports, '__esModule', { value: true });
  5 |
> 6 | var crypto = require('node:crypto');
    |                       ^
  7 |
  8 | function _interopNamespace(e) {
  9 |   if (e && e.__esModule) return e;

RCTFatal
__28-[RCTCxxBridge handleError:]_block_invoke
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_main_queue_drain
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
-[UIApplication _run]
UIApplicationMain
main
start_sim
0x0

Webpack 4 - Error on Compilation

Hello, i'm installing otpauth using Yarn 4
I use Webpack 4 as bundler but i have the following error during compilation:

[3] ERROR in ./node_modules/otpauth/dist/otpauth.esm.js 970:28
[3] Module parse failed: Unexpected token (970:28)
[3] You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
[3] | const randomBytes = size => {
[3] |   {
[3] >     if (!globalScope.crypto?.getRandomValues) {
[3] |       throw new Error("Cryptography API not available");
[3] |     }
[3]  @ ./js/app.js 40:0-35 153:17-29
[3]  @ ./js/main.js

Better approach in `uintToBuf` implementation

I was skimming through this repo to get idea for using Web Crypto API , and i saw buffer conversion for counter in both HOTP and TOTP generations...

I think here it can have bit shifting to right.

arr[i] = acc & 255;
acc -= arr[i];
acc /= 256;

Like this

arr[i] = acc & 255; 
acc >>= 8;

I was curious to know a rationale for having this division approach so i made this issue.

URI Parsing Issue

Hi,
I am trying to parse this URI -> otpauth://totp/Hello?secret=gyevisscogih3lbeaqfb3atrgcdaqcpo5ns3cs637uj4mo735q4tmwup&algorithm=SHA256&digits=6&period=30&lock=false
Getting error while parsing.

if (!Array.isArray(uriGroups)) {
        throw new URIError('Invalid URI format');
      } // Extract URI groups.

Generate from https://freeotp.github.io/qrcode.html
Google Authenticator App is able to parse this

nodejs with TOTP validation result always null

Greeting,

I use otpauth with nodejs version 20, and cant seem to make it working, the delta from code below is always null.

const base32_secret = "some_secret";
            let totp = new OTPAuth.TOTP({
                issuer: "190.244.20.10",
                label: "Pay",
                algorithm: "SHA1",
                digits: 6,
                secret: base32_secret,
                period:30,

            
            });
            let otpauth_url = totp.toString();
            let token = totp.generate();
            const delta = totp.validate({ token:token,timestamp:Date.now, window:1 });

thank you

More flexibility to the URL generation

https://github.com/hectorm/otpauth/blob/9c6029998f004473cfd82c8f9d2b028e307faa27/src/totp.js#L178C15-L191

I want to have a label and an issuer that results in this URL:

otpauth://totp/MyLabel?issuer=MyIssuer&secret=ABC123&algorithm=SHA1&digits=6&period=30

This looks best when you scan the QR code in Authy. MyLabel will be used as the label, while MyIssuer will be used by Authy to search for an appropriate logo.

However, with otpauth (as seen in the link above), this will generate something else for me:

otpauth://totp/MyIssuer:MyLabel?issuer=MyIssuer&secret=ABC123&algorithm=SHA1&digits=6&period=30

And this does not look as good in Authy. For now, I've decided to write the URL generation code myself, but it would be very nice if the library allowed me to do this, so I can simply use totp.toString().

How to solve this ?

file:///root/totpauth/node_modules/otpauth/dist/otpauth.node.mjs:77
  if (crypto?.createHmac) {
             ^

SyntaxError: Unexpected token '.'

image

How to get time left (counter)

Hi!

Some other OTP libraries provide counters which return the amount of seconds/time left of the current period. Is there some way to do this with otpauth? I'm not super familiar with how they work - Would you have some pointers as to how I could process the current period time left?

I'm using TOTP at the moment.

Use WebCrypto instead for performance & bundle size improvements

See #296 for original issue.


This package uses jsSHA as a dependency, which adds 21.4 kB to the bundle size (ESM minified). This is ~77.4% of this module's bundle size.

The WebCrypto API (including SubtleCrypto) is widely supported in browsers (while using HTTPS), and provides native implementations of crypto functions. Additionally, it supports all the currently supported algorithms in hmac-digest.js except SHA-224; however, only SHA-1, SHA-256, and SHA-512 are actually specified in the TOTP RFC.

Additionally, because the library's dependencies are currently bundled, jsSHA may be unnecessarily bundled in a final project multiple times if dependent projects contain files or dependencies that require jsSHA.

Since Web Crypto API is asynchronous only, this improvement could either be implemented as a breaking change, or with seperate asynchronous generate/validate methods that include the Web Crypto implementation.

Doubt about delta

when I use the validate method, it returns zero or -1, what does -1 mean, would -1 be a valid token? I appreciate if you can solve this doubt

Invalid Token

This might be a weird issue, but I'm currently facing this issue with only android devices. For iPhone, the whole process works seamlessly.
In the case of android, the QR is scanned correctly and the account is added to google authenticator. While verifying the token, it always fails. Tried the exact same procedure on iPhone and it worked perfectly right. Can you please help me as I have no clue about what is wrong?

The code is exactly the same as mentioned in the documentation.

Thanks in advance.

Problem on Deno

error: TS2339 [ERROR]: Property 'generate' does not exist on type 'TOTP'.

Getting Invalid OTP Code

Generate 2FA code is invalid and different as google authenticator .
Here is my code:

let code="screte_code_here";
 let totp = new OTPAuth.TOTP({
          
          algorithm: 'SHA1',
          digits: 6,
          secret: code,
        });
        // Generate a token.
        let token = totp.generate();
        let delta = totp.validate({
          token: token,
          window: 1
        });
        if(delta!=null){
         console.log("Valid Code")
        }
        

Sometime generated code is correct and sometime its wrong. Any idea why?

Invalid 'issuer' parameter

Hi! First thanks for the lib, it helps me a lot.

The problem,

I have this Issuer:
totp/Slack%20(Name%20Company%20Wonderfoul):[email protected]

When I get to the snippet:
uriParams.issuer === uriLabel[0]

I get the error: Invalid 'issuer' parameter;


Does the uriParams.issuer really need to be the same as the uriLabel?

I managed to handle it as follows:
uriLabel[0].split(" ")[0]

Or just ignore this condition.

What do you think?

QR Code

Thanks for this cool library. 🙏👍
But is there a reason that it does not use a QR code? Security? Code issues?
Maybe i am spoiled but...

Kind regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.