auth-verify 1.3.0 → 1.4.0
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 +8 -1
- package/package.json +2 -1
- package/readme.md +78 -3
- package/src/totp/base32.js +46 -0
- package/src/totp/index.js +66 -0
- package/tests/totpmanager.test.js +54 -0
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const JWTManager = require("./src/jwt");
|
|
|
2
2
|
const OTPManager = require("./src/otp");
|
|
3
3
|
const SessionManager = require("./src/session");
|
|
4
4
|
const OAuthManager = require("./src/oauth");
|
|
5
|
+
const TOTPManager = require("./src/totp");
|
|
5
6
|
|
|
6
7
|
class AuthVerify {
|
|
7
8
|
constructor(options = {}) {
|
|
@@ -12,7 +13,12 @@ class AuthVerify {
|
|
|
12
13
|
storeTokens = "memory",
|
|
13
14
|
otpHash = "sha256",
|
|
14
15
|
redisUrl,
|
|
15
|
-
useAlg
|
|
16
|
+
useAlg,
|
|
17
|
+
totp = {
|
|
18
|
+
digits: 6,
|
|
19
|
+
step: 30,
|
|
20
|
+
alg: "SHA1"
|
|
21
|
+
}
|
|
16
22
|
} = options;
|
|
17
23
|
|
|
18
24
|
// ✅ Ensure cookieName and secret always exist
|
|
@@ -35,6 +41,7 @@ class AuthVerify {
|
|
|
35
41
|
|
|
36
42
|
this.session = new SessionManager({ storeTokens, redisUrl });
|
|
37
43
|
this.oauth = new OAuthManager();
|
|
44
|
+
this.totp = new TOTPManager(totp);
|
|
38
45
|
|
|
39
46
|
this.senders = new Map();
|
|
40
47
|
}
|
package/package.json
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
"jsonwebtoken": "^9.0.2",
|
|
7
7
|
"node-telegram-bot-api": "^0.66.0",
|
|
8
8
|
"nodemailer": "^7.0.6",
|
|
9
|
+
"qrcode": "^1.5.4",
|
|
9
10
|
"redis": "^5.8.3",
|
|
10
11
|
"twilio": "^5.10.3",
|
|
11
12
|
"uuid": "^9.0.1"
|
|
12
13
|
},
|
|
13
14
|
"name": "auth-verify",
|
|
14
|
-
"version": "1.
|
|
15
|
+
"version": "1.4.0",
|
|
15
16
|
"description": "A simple Node.js library for sending and verifying OTP via email, SMS and Telegram bot. And handling JWT with Cookies",
|
|
16
17
|
"main": "index.js",
|
|
17
18
|
"scripts": {
|
package/readme.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**auth-verify** is a Node.js authentication utility that provides:
|
|
4
4
|
- ✅ Secure OTP (one-time password) generation and verification
|
|
5
5
|
- ✅ Sending OTPs via Email, SMS (pluggable helpers), and Telegram bot
|
|
6
|
+
- ✅ TOTP (Time-based One Time Passwords) generation code and QR code and verification (Google Authenticator support)
|
|
6
7
|
- ✅ JWT creation, verification and optional token revocation with memory/Redis storage
|
|
7
8
|
- ✅ Session management (in-memory or Redis)
|
|
8
9
|
- ✅ New: OAuth 2.0 integration for Google, Facebook, GitHub, X (Twitter) and Linkedin
|
|
@@ -48,7 +49,7 @@ const auth = new AuthVerify({
|
|
|
48
49
|
|
|
49
50
|
## 🔐 JWT Usage
|
|
50
51
|
|
|
51
|
-
### JWA Handling (
|
|
52
|
+
### JWA Handling (v1.3.0+)
|
|
52
53
|
|
|
53
54
|
You can choose json web algorithm for signing jwt
|
|
54
55
|
```js
|
|
@@ -215,6 +216,76 @@ auth.otp.verify({ check: 'user@example.com', code: '123456' }, (err, isValid)=>{
|
|
|
215
216
|
|
|
216
217
|
`resend` returns the new code (promise style) or calls callback.
|
|
217
218
|
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## ✅ TOTP (Time-based One Time Passwords) — Google Authenticator support (New in v1.4.0)
|
|
222
|
+
```js
|
|
223
|
+
const AuthVerify = require("auth-verify");
|
|
224
|
+
const auth = new AuthVerify();
|
|
225
|
+
// Optionally:
|
|
226
|
+
/*
|
|
227
|
+
const AuthVerify = require("auth-verify");
|
|
228
|
+
const auth = new AuthVerify({
|
|
229
|
+
totp: {
|
|
230
|
+
digits: 6 (default)
|
|
231
|
+
step: 30 (default)
|
|
232
|
+
alg: "SHA1" (default)
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
*/
|
|
236
|
+
```
|
|
237
|
+
You can change `digits`, `step`, `alg`.
|
|
238
|
+
- `digits`: how many digits your one-time password has **(Google Authenticator default = 6 digits)**
|
|
239
|
+
- `step`: how long each TOTP code lives in seconds **(Google Authenticator default = 30 seconds)**
|
|
240
|
+
- `alg`: the hashing algorithm used to generate the OTP **(Google Authenticator default = SHA1)**
|
|
241
|
+
### Generate secret
|
|
242
|
+
```js
|
|
243
|
+
const secret = auth.totp.secret();
|
|
244
|
+
console.log(secret); //base 32
|
|
245
|
+
```
|
|
246
|
+
### generate otpauth URI
|
|
247
|
+
```js
|
|
248
|
+
const uri = auth.totp.uri({
|
|
249
|
+
label: "user@example.com",
|
|
250
|
+
issuer: "AuthVerify",
|
|
251
|
+
secret
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
console.log(uri);
|
|
255
|
+
```
|
|
256
|
+
### generate QR code image
|
|
257
|
+
(send this PNG to frontend or show in UI)
|
|
258
|
+
```js
|
|
259
|
+
const qr = await auth.totp.qrcode(uri);
|
|
260
|
+
console.log(qr); // data:image/png;base64,...
|
|
261
|
+
```
|
|
262
|
+
### generate a TOTP code
|
|
263
|
+
```js
|
|
264
|
+
const token = auth.totp.generate(secret);
|
|
265
|
+
console.log("TOTP:", token);
|
|
266
|
+
```
|
|
267
|
+
### verify a code entered by user
|
|
268
|
+
```js
|
|
269
|
+
const ok = auth.totp.verify({ secret, token });
|
|
270
|
+
console.log(ok); // true or false
|
|
271
|
+
```
|
|
272
|
+
### example real flow
|
|
273
|
+
```js
|
|
274
|
+
// Register UI
|
|
275
|
+
const secret = auth.totp.secret();
|
|
276
|
+
const uri = auth.totp.uri({ label: "john@example.com", issuer: "AuthVerify", secret });
|
|
277
|
+
const qr = await auth.totp.qrcode(uri);
|
|
278
|
+
// show qr to user
|
|
279
|
+
|
|
280
|
+
// Then user scans QR with Google Authenticator
|
|
281
|
+
// Then user enters 6-digit code
|
|
282
|
+
const token = req.body.code;
|
|
283
|
+
|
|
284
|
+
// Verify
|
|
285
|
+
if (auth.totp.verify({ secret, token })) {
|
|
286
|
+
// enable 2FA
|
|
287
|
+
}
|
|
288
|
+
```
|
|
218
289
|
---
|
|
219
290
|
## 🌍 OAuth 2.0 Integration (v1.2.0+)
|
|
220
291
|
`auth.oauth` supports login via Google, Facebook, GitHub, X (Twitter) and Linkedin.
|
|
@@ -347,7 +418,7 @@ app.get("/auth/linkedin/callback", async (req, res)=>{
|
|
|
347
418
|
try{
|
|
348
419
|
const { code } = req.query;
|
|
349
420
|
const user = await linkedin.callback(code);
|
|
350
|
-
res.json({ success: true, provider: "
|
|
421
|
+
res.json({ success: true, provider: "linkedin", user });
|
|
351
422
|
}catch(err){
|
|
352
423
|
res.status(400).json({ error: err.message });
|
|
353
424
|
}
|
|
@@ -438,12 +509,15 @@ Notes:
|
|
|
438
509
|
auth-verify/
|
|
439
510
|
├─ README.md
|
|
440
511
|
├─ package.json
|
|
512
|
+
├─ index.js // exports AuthVerify
|
|
441
513
|
├─ src/
|
|
442
|
-
│ ├─ index.js // exports AuthVerify
|
|
443
514
|
│ ├─ jwt/
|
|
444
515
|
| | ├─ index.js
|
|
445
516
|
| | ├─ cookie/index.js
|
|
446
517
|
│ ├─ /otp/index.js
|
|
518
|
+
│ ├─ totp/
|
|
519
|
+
| | ├─ index.js
|
|
520
|
+
| | ├─ base32.js
|
|
447
521
|
│ ├─ /session/index.js
|
|
448
522
|
| ├─ /oauth/index.js
|
|
449
523
|
│ └─ helpers/helper.js
|
|
@@ -452,6 +526,7 @@ auth-verify/
|
|
|
452
526
|
│ ├─ jwtmanager.multitab.test.js
|
|
453
527
|
│ ├─ jwtmanager.test.js
|
|
454
528
|
│ ├─ otpmanager.test.js
|
|
529
|
+
│ ├─ totpmanager.test.js
|
|
455
530
|
├─ babel.config.js
|
|
456
531
|
```
|
|
457
532
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// base32.js
|
|
2
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
3
|
+
|
|
4
|
+
function encode(buffer) {
|
|
5
|
+
let bits = 0;
|
|
6
|
+
let value = 0;
|
|
7
|
+
let output = "";
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
10
|
+
value = (value << 8) | buffer[i];
|
|
11
|
+
bits += 8;
|
|
12
|
+
|
|
13
|
+
while (bits >= 5) {
|
|
14
|
+
output += alphabet[(value >>> (bits - 5)) & 31];
|
|
15
|
+
bits -= 5;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (bits > 0) {
|
|
20
|
+
output += alphabet[(value << (5 - bits)) & 31];
|
|
21
|
+
}
|
|
22
|
+
return output;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function decode(str) {
|
|
26
|
+
let bits = 0;
|
|
27
|
+
let value = 0;
|
|
28
|
+
const output = [];
|
|
29
|
+
|
|
30
|
+
for (const char of str.toUpperCase()) {
|
|
31
|
+
if (char === "=") break;
|
|
32
|
+
const idx = alphabet.indexOf(char);
|
|
33
|
+
if (idx === -1) continue;
|
|
34
|
+
|
|
35
|
+
value = (value << 5) | idx;
|
|
36
|
+
bits += 5;
|
|
37
|
+
|
|
38
|
+
if (bits >= 8) {
|
|
39
|
+
output.push((value >>> (bits - 8)) & 0xff);
|
|
40
|
+
bits -= 8;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Buffer.from(output);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { encode, decode };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// totp.js
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
const base32 = require("./base32");
|
|
4
|
+
|
|
5
|
+
class TOTPManager {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.step = options.step || 30;
|
|
8
|
+
this.digits = options.digits || 6;
|
|
9
|
+
this.algorithm = (options.alg || "SHA1").toUpperCase();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// generate random Base32 secret
|
|
13
|
+
secret(length = 20) {
|
|
14
|
+
const buf = crypto.randomBytes(length);
|
|
15
|
+
return base32.encode(buf);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// generate TOTP code
|
|
19
|
+
generate(secret) {
|
|
20
|
+
const time = Math.floor(Date.now() / 1000 / this.step);
|
|
21
|
+
return this._hotp(secret, time);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// verify code from user
|
|
25
|
+
verify(secret, code, window = 1) {
|
|
26
|
+
// check ± 1 window to allow delay
|
|
27
|
+
const time = Math.floor(Date.now() / 1000 / this.step);
|
|
28
|
+
for (let i = -window; i <= window; i++) {
|
|
29
|
+
const otp = this._hotp(secret, time + i);
|
|
30
|
+
if (otp === code) return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// build key URI for authenticator apps
|
|
36
|
+
uri({ label, secret, issuer }) {
|
|
37
|
+
return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(label)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=${this.algorithm}&digits=${this.digits}&period=${this.step}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// optional QR for CLI users (we can integrate QR lib later)
|
|
41
|
+
async qrcode(uri) {
|
|
42
|
+
const qrcode = require("qrcode");
|
|
43
|
+
return await qrcode.toDataURL(uri);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// internal HOTP algorithm
|
|
47
|
+
_hotp(secret, counter) {
|
|
48
|
+
const key = base32.decode(secret);
|
|
49
|
+
|
|
50
|
+
const buf = Buffer.alloc(8);
|
|
51
|
+
buf.writeUInt32BE(Math.floor(counter / Math.pow(2, 32)), 0);
|
|
52
|
+
buf.writeUInt32BE(counter & 0xffffffff, 4);
|
|
53
|
+
|
|
54
|
+
const hmac = crypto.createHmac(this.algorithm, key).update(buf).digest();
|
|
55
|
+
const offset = hmac[hmac.length - 1] & 0xf;
|
|
56
|
+
|
|
57
|
+
const code = ((hmac[offset] & 0x7f) << 24) |
|
|
58
|
+
((hmac[offset + 1] & 0xff) << 16) |
|
|
59
|
+
((hmac[offset + 2] & 0xff) << 8) |
|
|
60
|
+
(hmac[offset + 3] & 0xff);
|
|
61
|
+
|
|
62
|
+
return (code % 10 ** this.digits).toString().padStart(this.digits, "0");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = TOTPManager;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const AuthVerify = require('../index');
|
|
2
|
+
|
|
3
|
+
describe("TOTP Manager", () => {
|
|
4
|
+
let auth;
|
|
5
|
+
let secret;
|
|
6
|
+
|
|
7
|
+
beforeAll(() => {
|
|
8
|
+
auth = new AuthVerify();
|
|
9
|
+
secret = auth.totp.secret();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("should generate base32 secret", () => {
|
|
13
|
+
expect(typeof secret).toBe("string");
|
|
14
|
+
expect(secret.length).toBeGreaterThan(10);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("should generate 6 digit code", () => {
|
|
18
|
+
const code = auth.totp.generate(secret);
|
|
19
|
+
expect(code).toMatch(/^[0-9]{6}$/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("should verify valid totp", () => {
|
|
23
|
+
const code = auth.totp.generate(secret);
|
|
24
|
+
expect(auth.totp.verify(secret, code)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should reject invalid totp", () => {
|
|
28
|
+
const fake = "000000";
|
|
29
|
+
expect(auth.totp.verify(secret, fake)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should generate valid otpauth:// URL", () => {
|
|
33
|
+
const uri = auth.totp.uri({
|
|
34
|
+
label: "test@example.com",
|
|
35
|
+
issuer: "AuthVerify",
|
|
36
|
+
secret,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(uri.startsWith("otpauth://totp/")).toBe(true);
|
|
40
|
+
expect(uri.includes("secret=")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("should generate QR DataURL", async () => {
|
|
44
|
+
const uri = auth.totp.uri({
|
|
45
|
+
label: "qrtest@example.com",
|
|
46
|
+
issuer: "AuthVerify",
|
|
47
|
+
secret,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const url = await auth.totp.qrcode(uri);
|
|
51
|
+
|
|
52
|
+
expect(url.startsWith("data:image/png;base64,")).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|