@hiithere/otp-verification-module
v0.4.0
Published
Reusable Express OTP module with Redis storage, BullMQ delivery, validation, presets, and pluggable providers.
Maintainers
Readme
OTP Verification Module
Reusable Express OTP module for products that need OTP request, resend, and verify flows without rewriting the same backend code in every app.
It supports Redis active OTP storage, BullMQ notification jobs, Zod request validation, idempotency, rate limiting, WhatsApp delivery, metrics, queue dashboard wiring, and pluggable adapters.
Install
npm install @hiithere/otp-verification-moduleQuick Start
import express from 'express';
import { createOtpModule } from '@hiithere/otp-verification-module';
const app = express();
app.use(express.json());
const otpModule = createOtpModule({
preset: 'simple'
});
app.use('/otp', otpModule.router);
app.listen(3000);
process.on('SIGTERM', async () => {
await otpModule.close();
process.exit(0);
});Routes exposed by the module:
POST /otp/request
POST /otp/resend
POST /otp/verifyPresets
Use presets when you want sane defaults without manually wiring every class.
simple
Best for smaller apps that still want Redis-backed OTP state and BullMQ delivery, but do not need enterprise audit/locking behavior.
const otpModule = createOtpModule({
preset: 'simple'
});Simple preset uses:
RedisOtpRepository
RedisRateLimiter
RedisIdempotencyStore
BullMqNotificationQueue
NoOpDistributedLock
NoOpCooldownStore
NoOpAuditLoggerenterprise
Default preset. Best when multiple app instances may process OTP requests and you want stronger operational controls.
const otpModule = createOtpModule({
preset: 'enterprise'
});Enterprise preset uses:
RedisOtpRepository
RedisRateLimiter
RedisIdempotencyStore
BullMqNotificationQueue
RedisDistributedLock
RedisCooldownStore
MongoAuditLoggerConfiguration Object
Use config when the consuming app needs different Redis, queue, rate limit, cooldown, or OTP policy behavior.
const otpModule = createOtpModule({
preset: 'simple',
config: {
redis: {
url: process.env.REDIS_URL
},
queue: {
name: 'otp-notifications'
},
rateLimit: {
limit: 3,
windowSeconds: 60
},
idempotency: {
ttlSeconds: 300
},
cooldown: {
ttlSeconds: 30
},
otp: {
defaultPolicy: {
length: 6,
ttlSeconds: 300,
maxAttempts: 3
},
purposePolicies: {
LOGIN: {
ttlSeconds: 120
},
CHECKOUT: {
ttlSeconds: 60,
maxAttempts: 5
},
PASSWORD_RESET: {
ttlSeconds: 600,
maxAttempts: 3
}
}
}
}
});Shortcuts are also supported:
const otpModule = createOtpModule({
preset: 'simple',
redisUrl: process.env.REDIS_URL,
queueName: 'otp-notifications',
otpPolicies: {
LOGIN: {
ttlSeconds: 120,
maxAttempts: 3
}
}
});Policy values are merged with safe defaults, so a purpose can override only what it needs.
Worker
The API only queues the notification job. A worker must run separately to deliver OTP messages.
import {
createOtpNotificationWorker,
ClientNotificationProvider,
NotificationDispatcher,
OTP_CHANNEL,
WhatsappCloudProvider
} from '@hiithere/otp-verification-module';
createOtpNotificationWorker({
notificationProvider: new NotificationDispatcher({
providers: {
[OTP_CHANNEL.WHATSAPP]: new WhatsappCloudProvider({
accessToken: process.env.WHATSAPP_ACCESS_TOKEN,
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID,
templateName: process.env.WHATSAPP_TEMPLATE_NAME,
templateLanguage: process.env.WHATSAPP_TEMPLATE_LANGUAGE,
graphApiVersion: process.env.WHATSAPP_GRAPH_API_VERSION
})
}
})
});Client App Delivery
Use ClientNotificationProvider when the product using this package wants to deliver OTP through its own code. This is useful for in-app notifications, custom SMS vendors, internal queues, Firebase, sockets, or any delivery system owned by the consuming app.
import {
createClientOtpNotificationWorker
} from '@hiithere/otp-verification-module';
createClientOtpNotificationWorker({
providerName: 'my-product-in-app',
sendOtp: async ({ userId, recipient, otp, purpose, requestId }) => {
await myInAppNotificationService.send({
userId,
recipient,
title: 'Your verification code',
body: `Your ${purpose} OTP is ${otp}`,
metadata: {
requestId
}
});
return {
provider: 'my-product-in-app',
status: 'SENT'
};
}
});Then request OTP with:
{
"userId": "user_1",
"purpose": "LOGIN",
"channel": "IN_APP",
"recipient": "user_1"
}The service still owns OTP generation, hashing, storage, rate limiting, and verification. The client provider owns only delivery.
Consumer Integration Chain
Most apps need two small files.
API server:
import express from 'express';
import { createOtpModule } from '@hiithere/otp-verification-module';
const app = express();
const otpModule = createOtpModule({ preset: 'simple' });
app.use(express.json());
app.use('/otp', otpModule.router);
app.listen(3000);Worker:
import { createClientOtpNotificationWorker } from '@hiithere/otp-verification-module';
createClientOtpNotificationWorker({
sendOtp: async ({ userId, otp }) => {
await yourDeliveryService.sendOtp(userId, otp);
}
});Frontend:
POST /otp/request -> show OTP input
POST /otp/verify -> if OTP_VERIFIED, create session/JWT in your app
POST /otp/resend -> retry delivery after cooldown policy allows itThe package does not create users, sessions, JWTs, roles, or permissions. Your app owns those business decisions after OTP verification succeeds.
Request Examples
Request OTP:
curl -X POST http://localhost:3000/otp/request \
-H "Content-Type: application/json" \
-H "Idempotency-Key: login-user-1-001" \
-d '{
"userId": "user_1",
"purpose": "LOGIN",
"channel": "WHATSAPP",
"recipient": "+919999999999"
}'Verify OTP:
curl -X POST http://localhost:3000/otp/verify \
-H "Content-Type: application/json" \
-d '{
"userId": "user_1",
"purpose": "LOGIN",
"otp": "123456"
}'Environment
NODE_ENV=development
PORT=4002
API_PREFIX=/api/v1
MONGO_URI=mongodb://127.0.0.1:27017/otp_service
REDIS_URL=redis://127.0.0.1:6379
WHATSAPP_ACCESS_TOKEN=
WHATSAPP_PHONE_NUMBER_ID=
WHATSAPP_TEMPLATE_NAME=otp_login
WHATSAPP_TEMPLATE_LANGUAGE=en_US
WHATSAPP_GRAPH_API_VERSION=v20.0Redis is required for the default simple and enterprise presets. MongoDB is required only when using the default enterprise audit logger.
Adapter Overrides
Every main dependency can be replaced. This is the main plugin-style design point.
const otpModule = createOtpModule({
preset: 'simple',
otpRepository,
notificationQueue,
rateLimiter,
idempotencyStore,
auditLogger,
distributedLock,
cooldownStore,
otpPolicyService
});This lets one product use the default Redis/BullMQ setup while another product injects custom storage, custom SMS/WhatsApp providers, or no-op infrastructure.
Local Development
npm install
npm run dev
npm run worker:notificationsUseful local routes from the included app:
GET /health
GET /metrics
GET /admin/queues
POST /api/v1/otp/request
POST /api/v1/otp/resend
POST /api/v1/otp/verifyPublish Checklist
Before publishing:
npm run lint
npm test
npm run pack:dry-run
npm publishThe published package name is @hiithere/otp-verification-module.
Security Notes
OTP values are hashed before active storage. Raw OTP values are only passed to the notification queue/provider for delivery.
Do not expose Redis, MongoDB, BullMQ dashboard, or WhatsApp credentials publicly. Protect /admin/queues with authentication before using it in production.
Use HTTPS in production and keep idempotency keys unique per logical request.
Architecture
Route
-> Zod validation middleware
-> OtpController
-> OtpService
-> OtpPolicyService
-> OtpTokenService
-> OtpRepository
-> RateLimiter
-> IdempotencyStore
-> DistributedLock
-> CooldownStore
-> NotificationQueue
-> AuditLoggerThe code follows dependency injection and adapter-style interfaces so the OTP domain logic stays stable while infrastructure can change per product.
