0.4.1 • Published 11 months ago

@merzin/otp v0.4.1

Weekly downloads
-
License
MIT
Repository
gitlab
Last release
11 months ago

This is a utility to generate OTP (One-Time Password) codes. It can be used for two-factor authentication (2FA) or any other purpose where a one-time password is needed.

  • HOTP (HMAC-Based One-Time Password Algorithm): RFC 4226
  • TOTP (Time-Based One-Time Password Algorithm): RFC 6238

Features

  • Generate HOTP and TOTP codes.
  • Validate HOTP and TOTP codes.
  • Generate and parse OTP URIs.
  • Generate and parse OTP migration URIs.
  • Generate Base32 encoded secret.
  • Support for SHA1, SHA256 (SHA2), SHA512 (SHA2) and MD5 algorithms.
  • Support for custom code length and time step.
  • Support for Base32 encoded secret with padding or without padding.

Note: When implementing 2FA in your service, it is recommended to use TOTP with default options: secret with length of 20 bytes (32 characters of Base32), algorithm SHA1, digits 6 and period 30, as not all authenticator apps support other algorithms and options.

Note: When implementing an authenticator client, it is recommended to support both HOTP and TOTP and all algorithms and options to maximize compatibility with other services.

Usage

Library index @merzin/otp exports two (2) functions hotp and totp to generate HOTP and TOTP codes respectively. Rest of the package is divided into submodules for better bundle size and tree-shaking.

  • @merzin/otp
    • hotp function
    • totp function
  • @merzin/otp/types
    • OtpType type
    • OtpAlgorithm type
    • OtpParameters interface
  • @merzin/otp/generate
    • generateSecret function
  • @merzin/otp/validate
    • validateHotp function
    • validateTotp function
  • @merzin/otp/uri
    • encodeOtpUri function
    • parseOtpUri function
  • @merzin/otp/migration
    • encodeOtpMigrationUri function
    • parseOtpMigrationUri function

Generating HOTP code

Function hotp takes one (1) argument which is an object with the following properties:

  • secret: Base32 encoded secret (string)
  • algorithm: Algorithm to use (string?). Default is SHA1.
  • digits: Code length (number?). Default is 6.
  • counter: Counter (number?). Default is 0.
import { hotp } from "@merzin/otp";

const code: string = hotp({
  secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
  algorithm: "SHA1",
  digits: 6,
  counter: 0,
});

Generating TOTP code

Function totp takes two (2) arguments. The first argument is an object with the following properties:

  • secret: Base32 encoded secret (string)
  • algorithm: Algorithm to use (string?). Default is SHA1.
  • digits: Code length (number?). Default is 6.
  • period: Time step in seconds (number?). Default is 30.

The second argument is the time to use in milliseconds from epoch (number). Default is current time.

import { totp } from "@merzin/otp";

const code: string = totp(
  {
    secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
    algorithm: "SHA1",
    digits: 6,
    period: 30,
  },
  Date.now(),
);

Note: totp is a wrapper around hotp and uses the current time to calculate the counter counter = Math.floor(Date.now() / 1000 / period).

To calculate the next code, simply add the period to the current time Date.now() + period * 1000. Similarly, to calculate the previous code, subtract the period from the current time Date.now() - period * 1000. This is useful for verifying the code with a time window larger than given period.

Generate Secret

Function generateSecret takes two (2) optional arguments. The first argument is the length of the secret in bytes (number?). Default is 20 bytes (32 characters in Base32). The second argument is whether to pad the secret with "=" (boolean?). Default is false. The function returns a Base32 encoded secret.

import { generateSecret } from "@merzin/otp/generate";

const secret: string = generateSecret(20, false);

Validate HOTP

Function validateHotp takes three (3) arguments. The first argument is the HOTP parameters object (the same as in hotp function). The second argument is the code to validate (string). The third argument is an optional options object with the following properties:

  • acceptPrevious: Accept previous code(s) (boolean|number?). Default is false.
  • acceptNext: Accept next code(s) (boolean|number?). Default is true.

Function returns either null if the code is invalid or the updated counter (number) if the code is valid.

import { validateHotp } from "@merzin/otp/validate";

const updatedCounter: number | null = validateHotp(
  {
    secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
    algorithm: "SHA1",
    digits: 6,
    counter: 0,
  },
  "022694",
);

Validate TOTP

Function validateTotp takes three (3) arguments. The first argument is the TOTP parameters object (the same as in totp function). The second argument is the code to validate (string). The third argument is an optional options object with the following properties:

  • now: Time to use in milliseconds from epoch (number?). Default is current time.
  • acceptPrevious: Accept previous code(s) (boolean|number?). Default is true.
  • acceptNext: Accept next code(s) (boolean|number?). Default is true.

Function returns a boolean indicating whether the code is valid or not.

import { validateTotp } from "@merzin/otp/validate";

const valid: boolean = validateTotp(
  {
    secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
    algorithm: "SHA1",
    digits: 6,
    period: 30,
  },
  "605949",
);

OTP Parameters

Interface OtpParameters is used for encoding and decoding OTP parameters. It extends the hotp and totp objects. It has the following properties:

  • type: Type of OTP ("HOTP"|"TOTP").
  • secret: Base32 encoded secret (string).
  • name: Name of the OTP (string?).
  • issuer: Issuer of the OTP (string?).
  • algorithm: Algorithm to use ("SHA1"|"SHA256"|"SHA512"|"MD5"?).
  • digits: Code length (number?).
  • counter: Counter (number?). Should be used only for HOTP.
  • period: Time step in seconds (number?). Should be used only for TOTP.

Encode OTP URI

Function encodeOtpUri takes two (2) arguments. The first argument is OtpParameters object. The second argument is an optional boolean indicating whether to include the default options in the URI which by default is false. The function returns the OTP URI (string).

import { encodeOtpUri } from "@merzin/otp/uri";

const hotpUri: string = encodeOtpUri({
  type: "HOTP",
  secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
  name: "user@mail.tld",
  issuer: "example.com",
  algorithm: "SHA1",
  digits: 6,
  counter: 0,
});

const totpUri: string = encodeOtpUri({
  type: "TOTP",
  secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
  name: "user@mail.tld",
  issuer: "example.com",
  algorithm: "SHA1",
  digits: 6,
  period: 30,
});

Parse OTP URI

Function parseOtpUri takes one (1) argument which is the OTP URI (string). The function returns OtpParameters object. The function throws an error in the following cases:

  • URI protocol is not otpauth.
  • Type is not HOTP or TOTP (case insensitive).
  • Secret is not present.
  • Algorithm is not "SHA1", "SHA256", "SHA512" or "MD5" (case insensitive, ignores dashes).
import { parseOtpUri } from "@merzin/otp/uri";

const uri =
  "otpauth://hotp/example.com%3Auser%40mail.tld?secret=QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA&issuer=example.com&algorithm=SHA1&digits=6&counter=0";
const otpParameters: OtpParameters = parseOtpUri(uri);

Encode OTP Migration URI

Function encodeOtpMigrationUri takes two (2) arguments. The first argument is an array of OtpParameters objects. The second argument is an optional options object with the following properties:

  • autoFillDefaults: Whether to auto-fill default values (boolean?). Default is true.
  • batchId: Batch ID (number?). Default is current time in milliseconds from epoch.
  • entriesPerUri: Number of entries per URI (number?). Default is 10.
  • pad: Whether to pad the data with "=" (boolean?). Default is false.

Function returns the OTP migration URI (string).

Note: TOTP entries with period other than 30 seconds are ignored.

import { encodeOtpMigrationUri } from "@merzin/otp/migration";

const uris: string[] = encodeOtpMigrationUri([
  {
    name: "user@mail.tld",
    issuer: "example.com",
    secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
    type: "HOTP",
    algorithm: "SHA1",
    digits: 6,
    counter: 4,
  },
]);

Parse OTP Migration URI

Function parseOtpMigrationUri takes one (1) argument which is the OTP migration URI (string). The function returns an array of OtpParameters objects. The function throws an error in the following cases:

  • URI protocol is not otpauth-migration.
  • Data is not present.
import { parseOtpMigrationUri } from "@merzin/otp/migration";

const migrationUri =
  "otpauth-migration://offline?data=Ck8KFIRjDht5t6mPEK4cVFWx2uPdQrVAEg11c2VyQG1haWwudGxkGgtleGFtcGxlLmNvbSABKAEwATgEQhM3YWI4ZTUxNzQ1MDY0MzcyOTEwEAIYASAA";
const otpParametersList: OtpParameters[] = parseOtpMigrationUri(migrationUri);

Dependencies

  • rfc4648 (license MIT): Base32 decoding and encoding the secret and Base64 encoding and decoding the OTP migration URI data.
  • @noble/hashes (license MIT): HMAC and hashing algorithms SHA1, SHA256, SHA512 and MD5.
  • protobufjs (license BSD-3-Clause): Encoding and decoding the OTP parameters to and from migration URIs.

Changelog

All notable changes to this project will be documented in CHANGELOG.md.

License

MIT license. See LICENSE for details.

0.4.1

11 months ago

0.4.0

11 months ago

0.3.1

11 months ago

0.3.0

11 months ago

0.2.0

11 months ago

0.1.0

11 months ago