npm package: @mrmelphin/otp-lib
Description: OTP (One-Time Password) library with pluggable storage and delivery
Author: mrMelphin
Latest version: 0.1.0

# otp-lib-ts

Lightweight OTP (One-Time Password) library for TypeScript/Node.js with pluggable storage and delivery.

## Features

- Cryptographically secure numeric OTP generation (`crypto.randomInt`)
- SHA-256 hashed record storage (no plaintext codes persisted)
- One active OTP per login (upsert semantics)
- Configurable TTL and code length
- Pluggable `Repository` and `Sender` interfaces — bring your own storage and delivery
- Built-in repository implementations: In-Memory, PostgreSQL, SQLite, MongoDB

## Install

```bash
npm install @mrmelphin/otp-lib
```

## Quick Start

```typescript
import { OtpService, DEFAULT_CONFIG } from "@mrmelphin/otp-lib";
import type { Repository, Sender } from "@mrmelphin/otp-lib";

import { createMemoryRepository } from "@mrmelphin/otp-lib";

const repo = createMemoryRepository();
const sender: Sender = /* your implementation */;

const svc = new OtpService(repo, sender, DEFAULT_CONFIG);

// Generate and send OTP
const result = await svc.generate("user@example.com");
if (result.sendError) {
  console.warn("OTP generated but send failed:", result.sendError);
}

// Verify OTP
const valid = await svc.verify("user@example.com", result.code);
if (!valid) {
  throw new Error("Invalid or expired OTP");
}
console.log("OTP verified!");

// Clean up expired records (call periodically)
await svc.cleanExpired();
```

## Configuration

```typescript
import type { OtpConfig } from "@mrmelphin/otp-lib";

const config: OtpConfig = {
  ttl: 10 * 60 * 1000, // 10 minutes (default: 300000 = 5 min)
  otpLength: 8,         // default: 6, minimum: 4
};
```

## Interfaces

### Repository

```typescript
interface Repository {
  upsert(record: OtpRecord): Promise<void>;
  findByOtp(otpHash: string): Promise<OtpRecord | null>;
  deleteByOtpId(otpId: string): Promise<void>;
  deleteExpired(threshold: Date): Promise<void>;
}
```

### Sender

```typescript
interface Sender {
  send(login: string, code: string): Promise<void>;
}
```

## Data Model

```typescript
interface OtpRecord {
  otpId: string;     // SHA-256(login)
  otp: string;       // SHA-256(login + code)
  createdAt: Date;
}
```

## Repositories

The library ships with four ready-to-use repository implementations. Each can be imported from the main package or directly from `@mrmelphin/otp-lib/repo/*`. Driver dependencies are optional peer deps — install only what you need.

### In-Memory

Zero dependencies. Useful for tests and prototyping — data is lost on process restart.

```typescript
import { createMemoryRepository } from "@mrmelphin/otp-lib";

const repo = createMemoryRepository();
```

### PostgreSQL

Uses any `pg`-compatible client or pool.

```bash
npm install pg
```

```typescript
import { Pool } from "pg";
import { createPostgresRepository, createPostgresSchema } from "@mrmelphin/otp-lib";

const pool = new Pool({ connectionString: "postgres://localhost:5432/mydb" });

// Create table and indexes (call once, e.g. on app startup)
await createPostgresSchema(pool);

const repo = createPostgresRepository(pool);
// Optional: createPostgresRepository(pool, { tableName: "custom_otp" })
```

### SQLite

Uses `better-sqlite3` (synchronous driver, wrapped in async interface).

```bash
npm install better-sqlite3
```

```typescript
import Database from "better-sqlite3";
import { createSqliteRepository, createSqliteSchema } from "@mrmelphin/otp-lib";

const db = new Database("otp.db");

// Create table and indexes (call once)
createSqliteSchema(db);

const repo = createSqliteRepository(db);
// Optional: createSqliteRepository(db, { tableName: "custom_otp" })
```

### MongoDB

Uses the official `mongodb` driver — pass a `Collection`.

```bash
npm install mongodb
```

```typescript
import { MongoClient } from "mongodb";
import { createMongoRepository, createMongoSchema } from "@mrmelphin/otp-lib";

const client = new MongoClient("mongodb://localhost:27017");
const coll = client.db("mydb").collection("otp");

// Create indexes (call once)
await createMongoSchema(coll);

const repo = createMongoRepository(coll);
```

## Testing

```bash
npm test
```

## Design Decisions

- **SHA-256 without salt** — OTP codes are short-lived and single-use; the threat model differs from password storage.
- **Lookup by OTP hash** — verification uses `findByOtp(hash)` rather than looking up by login, preventing information leaks about active OTPs.
- **No rollback on send failure** — if sending fails, the record stays; the caller can retry via `generate` (upsert overwrites), and TTL handles expiration.

## License

MIT