auth-verify 1.2.5 → 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/index.js CHANGED
@@ -1,51 +1,43 @@
1
1
  const JWTManager = require("./src/jwt");
2
2
  const OTPManager = require("./src/otp");
3
3
  const SessionManager = require("./src/session");
4
- const OAuthManager = require("./src/oauth")
4
+ const OAuthManager = require("./src/oauth");
5
5
 
6
6
  class AuthVerify {
7
7
  constructor(options = {}) {
8
8
  let {
9
- jwtSecret,
9
+ jwtSecret = "jwt_secret",
10
+ cookieName = "jwt_token",
10
11
  otpExpiry = 300,
11
12
  storeTokens = "none",
12
13
  otpHash = "sha256",
13
14
  redisUrl,
14
15
  } = options;
15
16
 
16
- // if (!jwtSecret) throw new Error("jwtSecret is required in AuthVerify options");
17
- if (!jwtSecret) jwtSecret = 'JWT_AUTH_VERIFY_SECRET'
17
+ // Ensure cookieName and secret always exist
18
+ this.cookieName = cookieName;
19
+ this.jwtSecret = jwtSecret;
20
+
21
+ // ✅ Pass both into JWTManager
22
+ this.jwt = new JWTManager(jwtSecret, {
23
+ storeTokens,
24
+ cookieName,
25
+ });
18
26
 
19
- this.senderName;
20
- this.jwt = new JWTManager(jwtSecret, { storeTokens });
21
27
  this.otp = new OTPManager({
22
28
  storeTokens,
23
29
  otpExpiry,
24
30
  otpHash,
25
31
  redisUrl,
26
32
  });
33
+
27
34
  this.session = new SessionManager({ storeTokens, redisUrl });
35
+ this.oauth = new OAuthManager();
28
36
 
29
37
  this.senders = new Map();
30
- // this.register = {
31
- // sender: (name, fn)=>{
32
- // if (!name || typeof fn !== "function") {
33
- // throw new Error("Sender registration requires a name and a function");
34
- // }else{
35
- // try{
36
- // this.senders.set(name, fn);
37
- // }catch(err){
38
- // throw new Error(err);
39
- // }
40
- // }
41
- // }
42
- // }
43
- // ✅ No getters — directly reference otp.dev (it's a plain object)
44
-
45
- this.oauth = new OAuthManager();
46
38
  }
47
-
48
- // Session helpers
39
+
40
+ // --- Session helpers ---
49
41
  async createSession(userId, options = {}) {
50
42
  return this.session.create(userId, options);
51
43
  }
@@ -58,33 +50,23 @@ class AuthVerify {
58
50
  return this.session.destroy(sessionId);
59
51
  }
60
52
 
61
- async use(name){
62
- const senderFn = this.senders.get(name);
63
- if(!senderFn) throw new Error(`Sender "${name}" not found`);
64
- this.senderName = senderFn;
65
- }
53
+ // --- Sender registration ---
54
+ register = {
55
+ sender: (name, fn) => {
56
+ if (!name || typeof fn !== "function") {
57
+ throw new Error("Sender registration requires a name and a function");
58
+ }
59
+ this.senders.set(name, fn);
60
+ },
61
+ };
66
62
 
67
- register = {
68
- sender: (name, fn) => {
69
- if (!name || typeof fn !== "function") {
70
- throw new Error("Sender registration requires a name and a function");
71
- }
72
- this.senders.set(name, fn);
73
- // console.log(`✅ Sender registered: ${name}`);
74
- }
63
+ use(name) {
64
+ const senderFn = this.senders.get(name);
65
+ if (!senderFn) throw new Error(`Sender "${name}" not found`);
66
+ return {
67
+ send: async (options) => await senderFn(options),
75
68
  };
76
-
77
- // use a sender by name
78
- use(name) {
79
- const senderFn = this.senders.get(name);
80
- if (!senderFn) throw new Error(`Sender "${name}" not found`);
81
-
82
- return {
83
- send: async (options) => {
84
- return await senderFn(options); // call user function
85
- }
86
- };
87
- }
69
+ }
88
70
  }
89
71
 
90
- module.exports = AuthVerify;
72
+ module.exports = AuthVerify;
package/package.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "dependencies": {
3
- "auth-verify": "^1.2.4",
4
3
  "axios": "^1.12.2",
5
4
  "crypto": "^1.0.1",
6
5
  "ioredis": "^5.8.1",
@@ -12,7 +11,7 @@
12
11
  "uuid": "^9.0.1"
13
12
  },
14
13
  "name": "auth-verify",
15
- "version": "1.2.5",
14
+ "version": "1.2.7",
16
15
  "description": "A simple Node.js library for sending and verifying OTP via email, SMS and Telegram bot",
17
16
  "main": "index.js",
18
17
  "scripts": {
package/readme.md CHANGED
@@ -433,6 +433,11 @@ auth-verify/
433
433
  │ ├─ /session/index.js
434
434
  | ├─ /oauth/index.js
435
435
  │ └─ helpers/helper.js
436
+ ├─ test/
437
+ │ ├─ jwtmanager.multitab.test.js
438
+ │ ├─ jwtmanager.test.js
439
+ │ ├─ otpmanager.test.js
440
+ ├─ babel.config.js
436
441
  ```
437
442
 
438
443
  ---
package/src/jwt/index.js CHANGED
@@ -249,7 +249,8 @@ const CookieManager = require("./cookie");
249
249
  class JWTManager {
250
250
  constructor(secret, options = {}) {
251
251
  // if (!secret) throw new Error("JWT secret is required");
252
- this.secret = secret || "jwt_token";
252
+ this.secret = secret || "jwt_secret";
253
+ this.cookieName = options.cookieName || "jwt_token";
253
254
  this.storeType = options.storeTokens || "none";
254
255
 
255
256
  if (this.storeType === "memory") {
@@ -307,7 +308,7 @@ class JWTManager {
307
308
 
308
309
  // Auto cookie support
309
310
  if (options.res) {
310
- CookieManager.setCookie(options.res, this.secret, token, {
311
+ CookieManager.setCookie(options.res, this.cookieName, token, {
311
312
  httpOnly: true,
312
313
  secure: options.secure ?? true,
313
314
  sameSite: "Strict",
@@ -325,7 +326,7 @@ class JWTManager {
325
326
  // If request object provided
326
327
  if (typeof input === "object" && input.headers) {
327
328
  token =
328
- CookieManager.getCookie(input, this.secret) ||
329
+ CookieManager.getCookie(input, this.cookieName) ||
329
330
  (input.headers.authorization
330
331
  ? input.headers.authorization.replace("Bearer ", "")
331
332
  : null);
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(identifier, code);
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
- return this._verifyInternal(identifier, code);
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
- if (this.storeType === 'memory' || this.storeType === 'none') {
541
- const data = this.tokenStore ? this.tokenStore.get(identifier) : null;
542
- if (!data) throw new Error("OTP not found or expired");
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
- if (Date.now() > data.expiresAt) {
545
- this.tokenStore.delete(identifier);
546
- throw new Error("OTP expired");
547
- }
576
+ // if ((this.maxAttempts || 5) <= data.attempts) {
577
+ // throw new Error("Max attempts reached");
578
+ // }
548
579
 
549
- if ((this.maxAttempts || 5) <= data.attempts) {
550
- throw new Error("Max attempts reached");
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
- if (String(data.code) !== String(code)) {
554
- data.attempts = (data.attempts || 0) + 1;
555
- this.tokenStore.set(identifier, data);
556
- throw new Error("Invalid OTP");
557
- }
587
+ // // success
588
+ // this.tokenStore.delete(identifier);
589
+ // return true;
590
+ // }
558
591
 
559
- // success
560
- this.tokenStore.delete(identifier);
561
- return true;
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
- // redis
565
- if (this.storeType === 'redis') {
566
- const raw = await this.redis.get(identifier);
567
- if (!raw) throw new Error("OTP not found or expired");
568
- const data = JSON.parse(raw);
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
- if (Date.now() > data.expiresAt) {
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
- if ((this.maxAttempts || 5) <= (data.attempts || 0)) {
576
- throw new Error("Max attempts reached");
577
- }
638
+ // attempts check
639
+ if ((this.maxAttempts || 5) <= (data.attempts || 0)) {
640
+ throw new Error("Max attempts reached");
641
+ }
578
642
 
579
- if (String(data.code) !== String(code)) {
580
- data.attempts = (data.attempts || 0) + 1;
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
- throw new Error("{storeTokens} must be 'memory' or 'redis'");
662
+ return true;
591
663
  }
592
664
 
593
665
  cooldown(timestamp){
@@ -0,0 +1,34 @@
1
+ const express = require("express");
2
+ const request = require("supertest");
3
+ const AuthVerify = require("../index");
4
+
5
+ describe("JWT multi-tab verification", () => {
6
+ const auth = new AuthVerify({ jwtSecret: "test_secret", storeTokens: "memory" });
7
+ const app = express();
8
+
9
+ app.get("/login", async (req, res) => {
10
+ const token = await auth.jwt.sign({ userId: 1 }, "5s", { res });
11
+ res.json({ token });
12
+ });
13
+
14
+ app.get("/protected", async (req, res) => {
15
+ try {
16
+ const data = await auth.jwt.verify(req);
17
+ res.json({ valid: true, data });
18
+ } catch (err) {
19
+ res.status(401).json({ valid: false, error: err.message });
20
+ }
21
+ });
22
+
23
+ test("should work in second tab (same cookie)", async () => {
24
+ const loginRes = await request(app).get("/login");
25
+ const cookie = loginRes.headers["set-cookie"][0];
26
+
27
+ const protectedRes = await request(app)
28
+ .get("/protected")
29
+ .set("Cookie", cookie);
30
+
31
+ expect(protectedRes.body.valid).toBe(true);
32
+ expect(protectedRes.body.data.userId).toBe(1);
33
+ });
34
+ });
@@ -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
+ });