redis-otp-manager 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/cjs/adapters/memory.adapter.js +40 -0
- package/dist/cjs/adapters/redis.adapter.js +26 -0
- package/dist/cjs/core/otp.generator.js +14 -0
- package/dist/cjs/core/otp.hash.js +16 -0
- package/dist/cjs/core/otp.service.js +119 -0
- package/dist/cjs/core/otp.types.js +2 -0
- package/dist/cjs/errors/otp.errors.js +42 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/integrations/nest/index.js +8 -0
- package/dist/cjs/integrations/nest/otp.decorator.js +8 -0
- package/dist/cjs/integrations/nest/otp.module.js +92 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/utils/identifier-normalizer.js +30 -0
- package/dist/cjs/utils/key-builder.js +21 -0
- package/dist/cjs/utils/rate-limiter.js +13 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ This package currently includes:
|
|
|
16
16
|
- Optional resend cooldown
|
|
17
17
|
- Max-attempt protection
|
|
18
18
|
- NestJS module integration via `redis-otp-manager/nest`
|
|
19
|
+
- Dual package support for ESM and CommonJS consumers
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
|
@@ -29,6 +30,26 @@ For NestJS apps, also install the Nest peer dependencies used by your app:
|
|
|
29
30
|
npm install @nestjs/common @nestjs/core reflect-metadata rxjs
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
## Module Support
|
|
34
|
+
|
|
35
|
+
This package now supports both:
|
|
36
|
+
- ESM imports
|
|
37
|
+
- CommonJS/Nest `ts-node/register` style resolution
|
|
38
|
+
|
|
39
|
+
ESM:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { OTPManager } from "redis-otp-manager";
|
|
43
|
+
import { OTPModule } from "redis-otp-manager/nest";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
CommonJS:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const { OTPManager } = require("redis-otp-manager");
|
|
50
|
+
const { OTPModule } = require("redis-otp-manager/nest");
|
|
51
|
+
```
|
|
52
|
+
|
|
32
53
|
## Quality Checks
|
|
33
54
|
|
|
34
55
|
```bash
|
|
@@ -70,7 +91,7 @@ await otp.verify({
|
|
|
70
91
|
|
|
71
92
|
## NestJS
|
|
72
93
|
|
|
73
|
-
Import the Nest integration from the dedicated subpath so non-Nest users do not pull Nest dependencies.
|
|
94
|
+
Import the Nest integration from the dedicated subpath so non-Nest users do not pull Nest dependencies unless they need them.
|
|
74
95
|
|
|
75
96
|
```ts
|
|
76
97
|
import { Module } from "@nestjs/common";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryAdapter = void 0;
|
|
4
|
+
class MemoryAdapter {
|
|
5
|
+
storage = new Map();
|
|
6
|
+
async get(key) {
|
|
7
|
+
const record = this.storage.get(key);
|
|
8
|
+
if (!record) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (record.expiresAt <= Date.now()) {
|
|
12
|
+
this.storage.delete(key);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return record.value;
|
|
16
|
+
}
|
|
17
|
+
async set(key, value, ttlSeconds) {
|
|
18
|
+
this.storage.set(key, {
|
|
19
|
+
value,
|
|
20
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async del(key) {
|
|
24
|
+
this.storage.delete(key);
|
|
25
|
+
}
|
|
26
|
+
async increment(key, ttlSeconds) {
|
|
27
|
+
const currentRecord = this.storage.get(key);
|
|
28
|
+
if (!currentRecord || currentRecord.expiresAt <= Date.now()) {
|
|
29
|
+
await this.set(key, "1", ttlSeconds);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const nextValue = Number(currentRecord.value) + 1;
|
|
33
|
+
this.storage.set(key, {
|
|
34
|
+
value: String(nextValue),
|
|
35
|
+
expiresAt: currentRecord.expiresAt,
|
|
36
|
+
});
|
|
37
|
+
return nextValue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.MemoryAdapter = MemoryAdapter;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisAdapter = void 0;
|
|
4
|
+
class RedisAdapter {
|
|
5
|
+
client;
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
async get(key) {
|
|
10
|
+
return this.client.get(key);
|
|
11
|
+
}
|
|
12
|
+
async set(key, value, ttlSeconds) {
|
|
13
|
+
await this.client.set(key, value, { EX: ttlSeconds });
|
|
14
|
+
}
|
|
15
|
+
async del(key) {
|
|
16
|
+
await this.client.del(key);
|
|
17
|
+
}
|
|
18
|
+
async increment(key, ttlSeconds) {
|
|
19
|
+
const nextValue = await this.client.incr(key);
|
|
20
|
+
if (nextValue === 1) {
|
|
21
|
+
await this.client.expire(key, ttlSeconds);
|
|
22
|
+
}
|
|
23
|
+
return nextValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.RedisAdapter = RedisAdapter;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateNumericOtp = generateNumericOtp;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
function generateNumericOtp(length = 6) {
|
|
6
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
7
|
+
throw new TypeError("OTP length must be a positive integer.");
|
|
8
|
+
}
|
|
9
|
+
let otp = "";
|
|
10
|
+
for (let index = 0; index < length; index += 1) {
|
|
11
|
+
otp += (0, node_crypto_1.randomInt)(0, 10).toString();
|
|
12
|
+
}
|
|
13
|
+
return otp;
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashOtp = hashOtp;
|
|
4
|
+
exports.verifyOtpHash = verifyOtpHash;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
function hashOtp(otp) {
|
|
7
|
+
return (0, node_crypto_1.createHash)("sha256").update(otp).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
function verifyOtpHash(otp, expectedHash) {
|
|
10
|
+
const actualBuffer = Buffer.from(hashOtp(otp), "hex");
|
|
11
|
+
const expectedBuffer = Buffer.from(expectedHash, "hex");
|
|
12
|
+
if (actualBuffer.length !== expectedBuffer.length) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return (0, node_crypto_1.timingSafeEqual)(actualBuffer, expectedBuffer);
|
|
16
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTPManager = void 0;
|
|
4
|
+
const otp_generator_js_1 = require("./otp.generator.js");
|
|
5
|
+
const otp_hash_js_1 = require("./otp.hash.js");
|
|
6
|
+
const otp_errors_js_1 = require("../errors/otp.errors.js");
|
|
7
|
+
const key_builder_js_1 = require("../utils/key-builder.js");
|
|
8
|
+
const identifier_normalizer_js_1 = require("../utils/identifier-normalizer.js");
|
|
9
|
+
const rate_limiter_js_1 = require("../utils/rate-limiter.js");
|
|
10
|
+
class OTPManager {
|
|
11
|
+
options;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
validateManagerOptions(options);
|
|
14
|
+
this.options = {
|
|
15
|
+
...options,
|
|
16
|
+
otpLength: options.otpLength ?? 6,
|
|
17
|
+
devMode: options.devMode ?? false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async generate(input) {
|
|
21
|
+
validatePayload(input);
|
|
22
|
+
const normalizedInput = (0, identifier_normalizer_js_1.normalizePayloadIdentifier)(input, this.options.identifierNormalization);
|
|
23
|
+
const otpKey = (0, key_builder_js_1.buildOtpKey)(normalizedInput);
|
|
24
|
+
const attemptsKey = (0, key_builder_js_1.buildAttemptsKey)(normalizedInput);
|
|
25
|
+
const rateLimitKey = (0, key_builder_js_1.buildRateLimitKey)(normalizedInput);
|
|
26
|
+
const cooldownKey = (0, key_builder_js_1.buildCooldownKey)(normalizedInput);
|
|
27
|
+
if (this.options.resendCooldown) {
|
|
28
|
+
const cooldownActive = await this.options.store.get(cooldownKey);
|
|
29
|
+
if (cooldownActive) {
|
|
30
|
+
throw new otp_errors_js_1.OTPResendCooldownError();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await (0, rate_limiter_js_1.assertWithinRateLimit)(this.options.store, rateLimitKey, this.options.rateLimit);
|
|
34
|
+
const otp = (0, otp_generator_js_1.generateNumericOtp)(this.options.otpLength);
|
|
35
|
+
const hashedOtp = (0, otp_hash_js_1.hashOtp)(otp);
|
|
36
|
+
await this.options.store.set(otpKey, hashedOtp, this.options.ttl);
|
|
37
|
+
await this.options.store.del(attemptsKey);
|
|
38
|
+
if (this.options.resendCooldown) {
|
|
39
|
+
await this.options.store.set(cooldownKey, "1", this.options.resendCooldown);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
expiresIn: this.options.ttl,
|
|
43
|
+
otp: this.options.devMode ? otp : undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async verify(input) {
|
|
47
|
+
validatePayload(input);
|
|
48
|
+
const normalizedInput = (0, identifier_normalizer_js_1.normalizePayloadIdentifier)(input, this.options.identifierNormalization);
|
|
49
|
+
if (!input.otp || !input.otp.trim()) {
|
|
50
|
+
throw new TypeError("OTP must be a non-empty string.");
|
|
51
|
+
}
|
|
52
|
+
const otpKey = (0, key_builder_js_1.buildOtpKey)(normalizedInput);
|
|
53
|
+
const attemptsKey = (0, key_builder_js_1.buildAttemptsKey)(normalizedInput);
|
|
54
|
+
const storedHash = await this.options.store.get(otpKey);
|
|
55
|
+
if (!storedHash) {
|
|
56
|
+
throw new otp_errors_js_1.OTPExpiredError();
|
|
57
|
+
}
|
|
58
|
+
const isValid = (0, otp_hash_js_1.verifyOtpHash)(input.otp, storedHash);
|
|
59
|
+
if (!isValid) {
|
|
60
|
+
const attempts = await this.options.store.increment(attemptsKey, this.options.ttl);
|
|
61
|
+
if (attempts >= this.options.maxAttempts) {
|
|
62
|
+
await this.options.store.del(otpKey);
|
|
63
|
+
await this.options.store.del(attemptsKey);
|
|
64
|
+
throw new otp_errors_js_1.OTPMaxAttemptsExceededError();
|
|
65
|
+
}
|
|
66
|
+
throw new otp_errors_js_1.OTPInvalidError();
|
|
67
|
+
}
|
|
68
|
+
await this.options.store.del(otpKey);
|
|
69
|
+
await this.options.store.del(attemptsKey);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.OTPManager = OTPManager;
|
|
74
|
+
function validateManagerOptions(options) {
|
|
75
|
+
if (!Number.isInteger(options.ttl) || options.ttl <= 0) {
|
|
76
|
+
throw new TypeError("ttl must be a positive integer.");
|
|
77
|
+
}
|
|
78
|
+
if (!Number.isInteger(options.maxAttempts) || options.maxAttempts <= 0) {
|
|
79
|
+
throw new TypeError("maxAttempts must be a positive integer.");
|
|
80
|
+
}
|
|
81
|
+
if (options.otpLength !== undefined &&
|
|
82
|
+
(!Number.isInteger(options.otpLength) || options.otpLength <= 0)) {
|
|
83
|
+
throw new TypeError("otpLength must be a positive integer when provided.");
|
|
84
|
+
}
|
|
85
|
+
if (options.resendCooldown !== undefined &&
|
|
86
|
+
(!Number.isInteger(options.resendCooldown) || options.resendCooldown <= 0)) {
|
|
87
|
+
throw new TypeError("resendCooldown must be a positive integer when provided.");
|
|
88
|
+
}
|
|
89
|
+
if (options.rateLimit) {
|
|
90
|
+
if (!Number.isInteger(options.rateLimit.window) || options.rateLimit.window <= 0) {
|
|
91
|
+
throw new TypeError("rateLimit.window must be a positive integer.");
|
|
92
|
+
}
|
|
93
|
+
if (!Number.isInteger(options.rateLimit.max) || options.rateLimit.max <= 0) {
|
|
94
|
+
throw new TypeError("rateLimit.max must be a positive integer.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (options.identifierNormalization) {
|
|
98
|
+
const { trim, lowercase, preserveCaseFor } = options.identifierNormalization;
|
|
99
|
+
if (trim !== undefined && typeof trim !== "boolean") {
|
|
100
|
+
throw new TypeError("identifierNormalization.trim must be a boolean.");
|
|
101
|
+
}
|
|
102
|
+
if (lowercase !== undefined && typeof lowercase !== "boolean") {
|
|
103
|
+
throw new TypeError("identifierNormalization.lowercase must be a boolean.");
|
|
104
|
+
}
|
|
105
|
+
if (preserveCaseFor !== undefined &&
|
|
106
|
+
(!Array.isArray(preserveCaseFor) ||
|
|
107
|
+
preserveCaseFor.some((channel) => typeof channel !== "string" || !channel.trim()))) {
|
|
108
|
+
throw new TypeError("identifierNormalization.preserveCaseFor must be an array of non-empty strings.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function validatePayload(input) {
|
|
113
|
+
if (!input.type || !input.type.trim()) {
|
|
114
|
+
throw new TypeError("type must be a non-empty string.");
|
|
115
|
+
}
|
|
116
|
+
if (!input.identifier || !input.identifier.trim()) {
|
|
117
|
+
throw new TypeError("identifier must be a non-empty string.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTPResendCooldownError = exports.OTPMaxAttemptsExceededError = exports.OTPInvalidError = exports.OTPExpiredError = exports.OTPRateLimitExceededError = exports.OTPError = void 0;
|
|
4
|
+
class OTPError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, code) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = new.target.name;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.OTPError = OTPError;
|
|
13
|
+
class OTPRateLimitExceededError extends OTPError {
|
|
14
|
+
constructor(message = "OTP request rate limit exceeded.") {
|
|
15
|
+
super(message, "OTP_RATE_LIMIT_EXCEEDED");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.OTPRateLimitExceededError = OTPRateLimitExceededError;
|
|
19
|
+
class OTPExpiredError extends OTPError {
|
|
20
|
+
constructor(message = "OTP has expired or does not exist.") {
|
|
21
|
+
super(message, "OTP_EXPIRED");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.OTPExpiredError = OTPExpiredError;
|
|
25
|
+
class OTPInvalidError extends OTPError {
|
|
26
|
+
constructor(message = "OTP is invalid.") {
|
|
27
|
+
super(message, "OTP_INVALID");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.OTPInvalidError = OTPInvalidError;
|
|
31
|
+
class OTPMaxAttemptsExceededError extends OTPError {
|
|
32
|
+
constructor(message = "Maximum OTP verification attempts exceeded.") {
|
|
33
|
+
super(message, "OTP_MAX_ATTEMPTS_EXCEEDED");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.OTPMaxAttemptsExceededError = OTPMaxAttemptsExceededError;
|
|
37
|
+
class OTPResendCooldownError extends OTPError {
|
|
38
|
+
constructor(message = "OTP resend cooldown is active.") {
|
|
39
|
+
super(message, "OTP_RESEND_COOLDOWN");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.OTPResendCooldownError = OTPResendCooldownError;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTPResendCooldownError = exports.OTPRateLimitExceededError = exports.OTPMaxAttemptsExceededError = exports.OTPInvalidError = exports.OTPExpiredError = exports.OTPError = exports.MemoryAdapter = exports.RedisAdapter = exports.OTPManager = void 0;
|
|
4
|
+
var otp_service_js_1 = require("./core/otp.service.js");
|
|
5
|
+
Object.defineProperty(exports, "OTPManager", { enumerable: true, get: function () { return otp_service_js_1.OTPManager; } });
|
|
6
|
+
var redis_adapter_js_1 = require("./adapters/redis.adapter.js");
|
|
7
|
+
Object.defineProperty(exports, "RedisAdapter", { enumerable: true, get: function () { return redis_adapter_js_1.RedisAdapter; } });
|
|
8
|
+
var memory_adapter_js_1 = require("./adapters/memory.adapter.js");
|
|
9
|
+
Object.defineProperty(exports, "MemoryAdapter", { enumerable: true, get: function () { return memory_adapter_js_1.MemoryAdapter; } });
|
|
10
|
+
var otp_errors_js_1 = require("./errors/otp.errors.js");
|
|
11
|
+
Object.defineProperty(exports, "OTPError", { enumerable: true, get: function () { return otp_errors_js_1.OTPError; } });
|
|
12
|
+
Object.defineProperty(exports, "OTPExpiredError", { enumerable: true, get: function () { return otp_errors_js_1.OTPExpiredError; } });
|
|
13
|
+
Object.defineProperty(exports, "OTPInvalidError", { enumerable: true, get: function () { return otp_errors_js_1.OTPInvalidError; } });
|
|
14
|
+
Object.defineProperty(exports, "OTPMaxAttemptsExceededError", { enumerable: true, get: function () { return otp_errors_js_1.OTPMaxAttemptsExceededError; } });
|
|
15
|
+
Object.defineProperty(exports, "OTPRateLimitExceededError", { enumerable: true, get: function () { return otp_errors_js_1.OTPRateLimitExceededError; } });
|
|
16
|
+
Object.defineProperty(exports, "OTPResendCooldownError", { enumerable: true, get: function () { return otp_errors_js_1.OTPResendCooldownError; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InjectOTPManager = exports.OTP_MANAGER_OPTIONS = exports.OTPModule = void 0;
|
|
4
|
+
var otp_module_js_1 = require("./otp.module.js");
|
|
5
|
+
Object.defineProperty(exports, "OTPModule", { enumerable: true, get: function () { return otp_module_js_1.OTPModule; } });
|
|
6
|
+
Object.defineProperty(exports, "OTP_MANAGER_OPTIONS", { enumerable: true, get: function () { return otp_module_js_1.OTP_MANAGER_OPTIONS; } });
|
|
7
|
+
var otp_decorator_js_1 = require("./otp.decorator.js");
|
|
8
|
+
Object.defineProperty(exports, "InjectOTPManager", { enumerable: true, get: function () { return otp_decorator_js_1.InjectOTPManager; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InjectOTPManager = InjectOTPManager;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const otp_service_js_1 = require("../../core/otp.service.js");
|
|
6
|
+
function InjectOTPManager() {
|
|
7
|
+
return (0, common_1.Inject)(otp_service_js_1.OTPManager);
|
|
8
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
3
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
4
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
5
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
6
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7
|
+
var _, done = false;
|
|
8
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
9
|
+
var context = {};
|
|
10
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
11
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
12
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
13
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
14
|
+
if (kind === "accessor") {
|
|
15
|
+
if (result === void 0) continue;
|
|
16
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
17
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
18
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
19
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
20
|
+
}
|
|
21
|
+
else if (_ = accept(result)) {
|
|
22
|
+
if (kind === "field") initializers.unshift(_);
|
|
23
|
+
else descriptor[key] = _;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
27
|
+
done = true;
|
|
28
|
+
};
|
|
29
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
30
|
+
var useValue = arguments.length > 2;
|
|
31
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
32
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
33
|
+
}
|
|
34
|
+
return useValue ? value : void 0;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.OTPModule = exports.OTP_MANAGER_OPTIONS = void 0;
|
|
38
|
+
const common_1 = require("@nestjs/common");
|
|
39
|
+
const otp_service_js_1 = require("../../core/otp.service.js");
|
|
40
|
+
exports.OTP_MANAGER_OPTIONS = Symbol("OTP_MANAGER_OPTIONS");
|
|
41
|
+
let OTPModule = (() => {
|
|
42
|
+
let _classDecorators = [(0, common_1.Module)({})];
|
|
43
|
+
let _classDescriptor;
|
|
44
|
+
let _classExtraInitializers = [];
|
|
45
|
+
let _classThis;
|
|
46
|
+
var OTPModule = class {
|
|
47
|
+
static { _classThis = this; }
|
|
48
|
+
static {
|
|
49
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
50
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
51
|
+
OTPModule = _classThis = _classDescriptor.value;
|
|
52
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
53
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
54
|
+
}
|
|
55
|
+
static forRoot(options) {
|
|
56
|
+
const managerProvider = createManagerProvider(options);
|
|
57
|
+
return {
|
|
58
|
+
module: OTPModule,
|
|
59
|
+
global: options.isGlobal ?? false,
|
|
60
|
+
providers: [managerProvider],
|
|
61
|
+
exports: [otp_service_js_1.OTPManager],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
static forRootAsync(options) {
|
|
65
|
+
const optionsProvider = {
|
|
66
|
+
provide: exports.OTP_MANAGER_OPTIONS,
|
|
67
|
+
inject: options.inject ?? [],
|
|
68
|
+
useFactory: options.useFactory,
|
|
69
|
+
};
|
|
70
|
+
const managerProvider = {
|
|
71
|
+
provide: otp_service_js_1.OTPManager,
|
|
72
|
+
inject: [exports.OTP_MANAGER_OPTIONS],
|
|
73
|
+
useFactory: (resolvedOptions) => new otp_service_js_1.OTPManager(resolvedOptions),
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
module: OTPModule,
|
|
77
|
+
global: options.isGlobal ?? false,
|
|
78
|
+
imports: options.imports ?? [],
|
|
79
|
+
providers: [optionsProvider, managerProvider],
|
|
80
|
+
exports: [otp_service_js_1.OTPManager],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return OTPModule = _classThis;
|
|
85
|
+
})();
|
|
86
|
+
exports.OTPModule = OTPModule;
|
|
87
|
+
function createManagerProvider(options) {
|
|
88
|
+
return {
|
|
89
|
+
provide: otp_service_js_1.OTPManager,
|
|
90
|
+
useValue: new otp_service_js_1.OTPManager(options),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePayloadIdentifier = normalizePayloadIdentifier;
|
|
4
|
+
const DEFAULT_IDENTIFIER_NORMALIZATION = {
|
|
5
|
+
trim: true,
|
|
6
|
+
lowercase: true,
|
|
7
|
+
preserveCaseFor: ["sms", "token"],
|
|
8
|
+
};
|
|
9
|
+
function normalizePayloadIdentifier(payload, config) {
|
|
10
|
+
const resolvedConfig = resolveNormalizationConfig(config);
|
|
11
|
+
let identifier = payload.identifier;
|
|
12
|
+
if (resolvedConfig.trim) {
|
|
13
|
+
identifier = identifier.trim();
|
|
14
|
+
}
|
|
15
|
+
const preserveCase = resolvedConfig.preserveCaseFor.includes(payload.type);
|
|
16
|
+
if (resolvedConfig.lowercase && !preserveCase) {
|
|
17
|
+
identifier = identifier.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
...payload,
|
|
21
|
+
identifier,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function resolveNormalizationConfig(config) {
|
|
25
|
+
return {
|
|
26
|
+
...DEFAULT_IDENTIFIER_NORMALIZATION,
|
|
27
|
+
...config,
|
|
28
|
+
preserveCaseFor: config?.preserveCaseFor ?? DEFAULT_IDENTIFIER_NORMALIZATION.preserveCaseFor,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildOtpKey = buildOtpKey;
|
|
4
|
+
exports.buildAttemptsKey = buildAttemptsKey;
|
|
5
|
+
exports.buildRateLimitKey = buildRateLimitKey;
|
|
6
|
+
exports.buildCooldownKey = buildCooldownKey;
|
|
7
|
+
function normalizeIntent(intent) {
|
|
8
|
+
return intent ?? "default";
|
|
9
|
+
}
|
|
10
|
+
function buildOtpKey(payload) {
|
|
11
|
+
return `otp:${normalizeIntent(payload.intent)}:${payload.type}:${payload.identifier}`;
|
|
12
|
+
}
|
|
13
|
+
function buildAttemptsKey(payload) {
|
|
14
|
+
return `attempts:${normalizeIntent(payload.intent)}:${payload.type}:${payload.identifier}`;
|
|
15
|
+
}
|
|
16
|
+
function buildRateLimitKey(payload) {
|
|
17
|
+
return `rate:${payload.type}:${payload.identifier}`;
|
|
18
|
+
}
|
|
19
|
+
function buildCooldownKey(payload) {
|
|
20
|
+
return `cooldown:${normalizeIntent(payload.intent)}:${payload.type}:${payload.identifier}`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertWithinRateLimit = assertWithinRateLimit;
|
|
4
|
+
const otp_errors_js_1 = require("../errors/otp.errors.js");
|
|
5
|
+
async function assertWithinRateLimit(store, key, config) {
|
|
6
|
+
if (!config) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const nextCount = await store.increment(key, config.window);
|
|
10
|
+
if (nextCount > config.max) {
|
|
11
|
+
throw new otp_errors_js_1.OTPRateLimitExceededError();
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redis-otp-manager",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Lightweight, Redis-backed OTP manager for Node.js apps",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,16 +13,20 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Prakash",
|
|
15
15
|
"type": "module",
|
|
16
|
-
"main": "./dist/index.js",
|
|
16
|
+
"main": "./dist/cjs/index.js",
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
20
20
|
"types": "./dist/index.d.ts",
|
|
21
|
-
"import": "./dist/index.js"
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/cjs/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
22
24
|
},
|
|
23
25
|
"./nest": {
|
|
24
26
|
"types": "./dist/integrations/nest/index.d.ts",
|
|
25
|
-
"import": "./dist/integrations/nest/index.js"
|
|
27
|
+
"import": "./dist/integrations/nest/index.js",
|
|
28
|
+
"require": "./dist/cjs/integrations/nest/index.js",
|
|
29
|
+
"default": "./dist/integrations/nest/index.js"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
@@ -32,7 +36,7 @@
|
|
|
32
36
|
],
|
|
33
37
|
"scripts": {
|
|
34
38
|
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
35
|
-
"build": "npm run clean && tsc -p tsconfig.json",
|
|
39
|
+
"build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/prepare-cjs.cjs",
|
|
36
40
|
"test": "tsx --test test/**/*.test.ts",
|
|
37
41
|
"check": "npm run build && npm run test",
|
|
38
42
|
"prepublishOnly": "npm run build"
|