auth-verify 1.2.6 → 1.2.7
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/package.json +1 -3
- package/readme.md +5 -0
- package/src/otp/index.js +113 -41
- package/tests/otpmanager.test.js +51 -0
package/package.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"auth-verify": "^1.2.4",
|
|
4
3
|
"axios": "^1.12.2",
|
|
5
4
|
"crypto": "^1.0.1",
|
|
6
|
-
"express": "^5.1.0",
|
|
7
5
|
"ioredis": "^5.8.1",
|
|
8
6
|
"jsonwebtoken": "^9.0.2",
|
|
9
7
|
"node-telegram-bot-api": "^0.66.0",
|
|
@@ -13,7 +11,7 @@
|
|
|
13
11
|
"uuid": "^9.0.1"
|
|
14
12
|
},
|
|
15
13
|
"name": "auth-verify",
|
|
16
|
-
"version": "1.2.
|
|
14
|
+
"version": "1.2.7",
|
|
17
15
|
"description": "A simple Node.js library for sending and verifying OTP via email, SMS and Telegram bot",
|
|
18
16
|
"main": "index.js",
|
|
19
17
|
"scripts": {
|
package/readme.md
CHANGED
package/src/otp/index.js
CHANGED
|
@@ -519,75 +519,147 @@ class OTPManager {
|
|
|
519
519
|
}
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
-
async verify({ check: identifier, code }, callback) {
|
|
522
|
+
// async verify({ check: identifier, code }, callback) {
|
|
523
|
+
// // callback style
|
|
524
|
+
// if (typeof callback === 'function') {
|
|
525
|
+
// try {
|
|
526
|
+
// const res = await this._verifyInternal(identifier, code);
|
|
527
|
+
// return callback(null, res);
|
|
528
|
+
// } catch (err) {
|
|
529
|
+
// return callback(err);
|
|
530
|
+
// }
|
|
531
|
+
// }
|
|
532
|
+
|
|
533
|
+
// // promise style
|
|
534
|
+
// return this._verifyInternal(identifier, code);
|
|
535
|
+
// }
|
|
536
|
+
|
|
537
|
+
async verify({ check, code }, callback) {
|
|
538
|
+
const handleError = (err) => {
|
|
539
|
+
// normalize message
|
|
540
|
+
if (err.message.includes("expired")) err = new Error("OTP expired");
|
|
541
|
+
else if (err.message.includes("Invalid")) err = new Error("Invalid OTP");
|
|
542
|
+
return err;
|
|
543
|
+
};
|
|
544
|
+
|
|
523
545
|
// callback style
|
|
524
|
-
if (typeof callback === 'function') {
|
|
546
|
+
if (callback && typeof callback === 'function') {
|
|
525
547
|
try {
|
|
526
|
-
const res = await this._verifyInternal(
|
|
548
|
+
const res = await this._verifyInternal(check, code);
|
|
527
549
|
return callback(null, res);
|
|
528
550
|
} catch (err) {
|
|
529
|
-
return callback(err);
|
|
551
|
+
return callback(handleError(err));
|
|
530
552
|
}
|
|
531
553
|
}
|
|
532
554
|
|
|
533
555
|
// promise style
|
|
534
|
-
|
|
556
|
+
try {
|
|
557
|
+
return await this._verifyInternal(check, code);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
throw handleError(err);
|
|
560
|
+
}
|
|
535
561
|
}
|
|
536
562
|
|
|
563
|
+
|
|
537
564
|
// helper used by verify()
|
|
538
|
-
async _verifyInternal(identifier, code) {
|
|
539
|
-
// memory
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
565
|
+
// async _verifyInternal(identifier, code) {
|
|
566
|
+
// // memory
|
|
567
|
+
// if (this.storeType === 'memory' || this.storeType === 'none') {
|
|
568
|
+
// const data = this.tokenStore ? this.tokenStore.get(identifier) : null;
|
|
569
|
+
// if (!data) throw new Error("OTP not found or expired");
|
|
570
|
+
|
|
571
|
+
// if (Date.now() > data.expiresAt) {
|
|
572
|
+
// this.tokenStore.delete(identifier);
|
|
573
|
+
// throw new Error("OTP expired");
|
|
574
|
+
// }
|
|
543
575
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
576
|
+
// if ((this.maxAttempts || 5) <= data.attempts) {
|
|
577
|
+
// throw new Error("Max attempts reached");
|
|
578
|
+
// }
|
|
548
579
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
580
|
+
// if (String(data.code) !== String(code)) {
|
|
581
|
+
// data.attempts = (data.attempts || 0) + 1;
|
|
582
|
+
// this.tokenStore.set(identifier, data);
|
|
583
|
+
// throw new Error("Invalid OTP");
|
|
584
|
+
// // return false;
|
|
585
|
+
// }
|
|
552
586
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
587
|
+
// // success
|
|
588
|
+
// this.tokenStore.delete(identifier);
|
|
589
|
+
// return true;
|
|
590
|
+
// }
|
|
558
591
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
592
|
+
// // redis
|
|
593
|
+
// if (this.storeType === 'redis') {
|
|
594
|
+
// const raw = await this.redis.get(identifier);
|
|
595
|
+
// if (!raw) throw new Error("OTP not found or expired");
|
|
596
|
+
// const data = JSON.parse(raw);
|
|
597
|
+
|
|
598
|
+
// if (Date.now() > data.expiresAt) {
|
|
599
|
+
// await this.redis.del(identifier);
|
|
600
|
+
// throw new Error("OTP expired");
|
|
601
|
+
// }
|
|
602
|
+
|
|
603
|
+
// if ((this.maxAttempts || 5) <= (data.attempts || 0)) {
|
|
604
|
+
// throw new Error("Max attempts reached");
|
|
605
|
+
// }
|
|
563
606
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
607
|
+
// if (String(data.code) !== String(code)) {
|
|
608
|
+
// data.attempts = (data.attempts || 0) + 1;
|
|
609
|
+
// const remaining = Math.max(0, Math.floor((data.expiresAt - Date.now()) / 1000));
|
|
610
|
+
// await this.redis.set(identifier, JSON.stringify(data), 'EX', remaining);
|
|
611
|
+
// throw new Error("Invalid OTP");
|
|
612
|
+
// }
|
|
613
|
+
|
|
614
|
+
// await this.redis.del(identifier);
|
|
615
|
+
// return true;
|
|
616
|
+
// }
|
|
569
617
|
|
|
570
|
-
|
|
618
|
+
// throw new Error("{storeTokens} must be 'memory' or 'redis'");
|
|
619
|
+
// }
|
|
620
|
+
|
|
621
|
+
async _verifyInternal(identifier, code) {
|
|
622
|
+
const data = this.storeType === 'memory' || this.storeType === 'none'
|
|
623
|
+
? this.tokenStore?.get(identifier)
|
|
624
|
+
: JSON.parse(await this.redis.get(identifier) || 'null');
|
|
625
|
+
|
|
626
|
+
if (!data) throw new Error("OTP not found or expired");
|
|
627
|
+
|
|
628
|
+
// strict expiry check
|
|
629
|
+
if (Date.now() >= data.expiresAt) {
|
|
630
|
+
if (this.storeType === 'memory' || this.storeType === 'none') {
|
|
631
|
+
this.tokenStore.delete(identifier);
|
|
632
|
+
} else {
|
|
571
633
|
await this.redis.del(identifier);
|
|
572
|
-
throw new Error("OTP expired");
|
|
573
634
|
}
|
|
635
|
+
throw new Error("OTP expired");
|
|
636
|
+
}
|
|
574
637
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
638
|
+
// attempts check
|
|
639
|
+
if ((this.maxAttempts || 5) <= (data.attempts || 0)) {
|
|
640
|
+
throw new Error("Max attempts reached");
|
|
641
|
+
}
|
|
578
642
|
|
|
579
|
-
|
|
580
|
-
|
|
643
|
+
// incorrect code
|
|
644
|
+
if (String(data.code) !== String(code)) {
|
|
645
|
+
data.attempts = (data.attempts || 0) + 1;
|
|
646
|
+
if (this.storeType === 'memory' || this.storeType === 'none') {
|
|
647
|
+
this.tokenStore.set(identifier, data);
|
|
648
|
+
} else {
|
|
581
649
|
const remaining = Math.max(0, Math.floor((data.expiresAt - Date.now()) / 1000));
|
|
582
650
|
await this.redis.set(identifier, JSON.stringify(data), 'EX', remaining);
|
|
583
|
-
throw new Error("Invalid OTP");
|
|
584
651
|
}
|
|
652
|
+
throw new Error("Invalid OTP");
|
|
653
|
+
}
|
|
585
654
|
|
|
655
|
+
// ✅ success
|
|
656
|
+
if (this.storeType === 'memory' || this.storeType === 'none') {
|
|
657
|
+
this.tokenStore.delete(identifier);
|
|
658
|
+
} else {
|
|
586
659
|
await this.redis.del(identifier);
|
|
587
|
-
return true;
|
|
588
660
|
}
|
|
589
661
|
|
|
590
|
-
|
|
662
|
+
return true;
|
|
591
663
|
}
|
|
592
664
|
|
|
593
665
|
cooldown(timestamp){
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
jest.setTimeout(15000);
|
|
2
|
+
|
|
3
|
+
const AuthVerify = require('../index');
|
|
4
|
+
const delay = ms => new Promise(r => setTimeout(r, ms));
|
|
5
|
+
|
|
6
|
+
describe('OTPManager', () => {
|
|
7
|
+
let auth;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
auth = new AuthVerify({
|
|
11
|
+
storeTokens: 'memory', // you can also test with 'redis'
|
|
12
|
+
otpExpiry: 1, // 1 second expiry
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('should generate and verify OTP successfully', async () => {
|
|
17
|
+
const email = 'test@example.com';
|
|
18
|
+
|
|
19
|
+
// generate OTP
|
|
20
|
+
const otp = await auth.otp.generate().set(email);
|
|
21
|
+
expect(typeof otp.code).toBe('string');
|
|
22
|
+
expect(otp.code.length).toBeGreaterThan(3);
|
|
23
|
+
|
|
24
|
+
// verify OTP
|
|
25
|
+
const isValid = await auth.otp.verify({check: email, code: otp.code});
|
|
26
|
+
expect(isValid).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// jest.useFakeTimers();
|
|
30
|
+
|
|
31
|
+
test('should reject wrong OTP', async () => {
|
|
32
|
+
const email = 'fake@example.com';
|
|
33
|
+
await auth.otp.generate().set(email);
|
|
34
|
+
await expect(auth.otp.verify({check: email, code: '000000'})).rejects.toThrow('Invalid OTP');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should expire OTP after time limit', async () => {
|
|
38
|
+
const email = 'expire@test.com';
|
|
39
|
+
const otp = await auth.otp.generate().set(email);
|
|
40
|
+
|
|
41
|
+
// await delay(3000); // wait longer than expiry
|
|
42
|
+
|
|
43
|
+
setTimeout(async () => {
|
|
44
|
+
try {
|
|
45
|
+
await expect(auth.otp.verify({ check: email, code: otp.code })).rejects.toThrow('OTP expired');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// console.error("🔴 Expired as expected:", err.message);
|
|
48
|
+
}
|
|
49
|
+
}, 3000);
|
|
50
|
+
});
|
|
51
|
+
});
|