npm package: @merzin/otp
Description: One-Time Password (OTP) generator and validator with support for algorithms SHA1, SHA256, SHA512 and MD5.
Latest version: 0.4.1

<h1>
  <span>OTP&nbsp;</span>
  <a href="https://www.npmjs.com/package/@merzin/otp">
    <img alt="Version" src="https://img.shields.io/npm/v/@merzin/otp">
  </a>
  <a href="https://www.npmjs.com/package/@merzin/otp">
    <img alt="Downloads" src="https://img.shields.io/npm/dm/@merzin/otp">
  </a>
  <a href="https://gitlab.com/merzin/otp/-/blob/main/LICENSE">
    <img alt="License" src="https://img.shields.io/gitlab/license/merzin/otp">
  </a>
  <a href="https://gitlab.com/merzin/otp/-/pipelines">
    <img alt="Pipeline" src="https://img.shields.io/gitlab/pipeline-status/merzin/otp">
  </a>
</h1>

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](http://tools.ietf.org/html/rfc4226)
- TOTP (Time-Based One-Time Password Algorithm):
  [RFC 6238](http://tools.ietf.org/html/rfc6238)

## 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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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`](#otp-parameters) 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).

```typescript
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`](#otp-parameters) 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).

```typescript
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`](#otp-parameters) 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.

```typescript
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`](#otp-parameters) objects. The function throws an error in the
following cases:

- URI protocol is not `otpauth-migration`.
- Data is not present.

```typescript
import { parseOtpMigrationUri } from "@merzin/otp/migration";

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

## Dependencies

- [rfc4648](https://www.npmjs.com/package/rfc4648) (license MIT): Base32
  decoding and encoding the secret and Base64 encoding and decoding the
  OTP migration URI data.
- [@noble/hashes](https://www.npmjs.com/package/@noble/hashes) (license MIT):
  HMAC and hashing algorithms SHA1, SHA256, SHA512 and MD5.
- [protobufjs](https://www.npmjs.com/package/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](https://gitlab.com/merzin/otp/-/blob/main/CHANGELOG.md).

## License

MIT license. See [LICENSE](https://gitlab.com/merzin/otp/-/blob/main/LICENSE) for details.