auth-verify 0.0.2 → 1.1.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.
@@ -0,0 +1,1052 @@
1
+ const {generateSecureOTP, resendGeneratedOTP, sendSMS} = require('../helpers/helper');
2
+ const Redis = require("ioredis");
3
+ const nodemailer = require("nodemailer");
4
+ const TelegramBot = require("node-telegram-bot-api");
5
+
6
+ class OTPManager {
7
+ constructor(otpOptions = {}){
8
+ if(typeof otpOptions.otpExpiry == 'string'){
9
+ let timeValue = parseInt(otpOptions.otpExpiry);
10
+ if(otpOptions.otpExpiry.endsWith('s')) this.otpExpiry = timeValue * 1000;
11
+ else if(otpOptions.otpExpiry.endsWith('m')) this.otpExpiry = timeValue * 1000 * 60;
12
+ }else{
13
+ this.otpExpiry = (otpOptions.otpExpiry || 300) * 1000;
14
+ }
15
+ this.storeType = otpOptions.storeTokens || 'none';
16
+ this.hashAlgorithm = otpOptions.otpHash || 'sha256';
17
+ this.logger = null;
18
+ this.customSender = null;
19
+
20
+ if(this.storeType !== 'none'){
21
+ if(this.storeType == 'memory'){
22
+ this.tokenStore = new Map();
23
+ }else if(this.storeType == 'redis'){
24
+ this.redis = new Redis(otpOptions.redisUrl || "redis://localhost:6379");
25
+ }else{
26
+ throw new Error("⚠️ {storeTokens} should be 'none', 'memory', or 'redis'");
27
+ }
28
+ }
29
+
30
+ }
31
+
32
+ // generate(length = 6, callback) {
33
+ // try {
34
+ // const code = generateSecureOTP(length, this.hashAlgorithm);
35
+ // this.code = code;
36
+
37
+ // if (callback && typeof callback === 'function') {
38
+ // callback(null, code);
39
+ // }
40
+
41
+ // return this; // always return instance for chaining
42
+ // } catch (err) {
43
+ // if (callback && typeof callback === 'function') callback(err);
44
+ // else throw err;
45
+ // }
46
+ // }
47
+
48
+ setSender(config){
49
+ if(!config.via) throw new Error("⚠️ Sender type { via } is required. It shouldbe 'email' or 'sms' or 'telegram'");
50
+ this.senderConfig = config;
51
+ }
52
+
53
+ async set(receiverEmailorPhone, callback){
54
+ const expiryInSeconds = Math.floor(this.otpExpiry / 1000);
55
+
56
+ if(callback && typeof callback == 'function'){
57
+ if(this.storeType == 'memory'){
58
+ this.tokenStore.set(receiverEmailorPhone, {
59
+ code: this.code,
60
+ createdAt: Date.now(),
61
+ expiresAt: Date.now() + this.otpExpiry,
62
+ attempts: 0,
63
+ cooldownUntil: 0
64
+ });
65
+
66
+ callback(null)
67
+ }else if(this.storeType == 'redis'){
68
+ callback(new Error('⚠️ Redis requires async/await. Use Promise style.'));
69
+ }else{
70
+ callback(new Error("❌ {storeTokens} should be 'memory' or 'redis' or 'none'"));
71
+ }
72
+ return;
73
+ }
74
+
75
+ try {
76
+ if(this.storeType == 'memory'){
77
+ this.tokenStore.set(receiverEmailorPhone, {
78
+ code: this.code,
79
+ createdAt: Date.now(),
80
+ expiresAt: Date.now() + this.otpExpiry,
81
+ attempts: 0,
82
+ cooldownUntil: 0
83
+ });
84
+
85
+ return this;
86
+ }else if(this.storeType == 'redis'){
87
+ await this.redis.set(receiverEmailorPhone, JSON.stringify({
88
+ code,
89
+ createdAt: Date.now(),
90
+ expiresAt: Date.now() + this.otpExpiry,
91
+ attempts: 0
92
+ }), "EX", expiryInSeconds);
93
+
94
+ return this;
95
+ }else{
96
+ throw new Error("❌ {storeTokens} should be 'memory' or 'redis'");
97
+ }
98
+ }catch(err){
99
+ throw new Error(err);
100
+ }
101
+ }
102
+
103
+ // async message(options, callback) {
104
+ // try {
105
+ // const { to, subject, text, html } = options;
106
+ // let transporter;
107
+
108
+ // if (this.senderConfig.via === "email") {
109
+ // if (this.senderConfig.service === "gmail") {
110
+ // transporter = nodemailer.createTransport({
111
+ // service: 'gmail',
112
+ // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass },
113
+ // });
114
+ // } else if (this.senderConfig.service === "smtp") {
115
+ // transporter = nodemailer.createTransport({
116
+ // host: this.senderConfig.host,
117
+ // port: this.senderConfig.port,
118
+ // secure: this.senderConfig.secure,
119
+ // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass },
120
+ // });
121
+ // } else {
122
+ // throw new Error(`❌ Unsupported email service: ${this.senderConfig.service}`);
123
+ // }
124
+
125
+ // const info = await transporter.sendMail({ from: this.senderConfig.sender, to, subject, text, html });
126
+
127
+ // if (callback && typeof callback === "function") return callback(null, info);
128
+ // return info;
129
+ // }
130
+
131
+ // throw new Error(`❌ Unsupported via type: ${via}`);
132
+ // } catch (err) {
133
+ // if (callback && typeof callback === "function") return callback(err);
134
+ // throw err;
135
+ // }
136
+ // }
137
+
138
+ maxAttempt(limit) {
139
+ this.maxAttempts = limit;
140
+ }
141
+
142
+ // async verify({check, code}, callback){
143
+ // if(callback && typeof callback == 'function'){
144
+ // if(this.storeType == 'memory'){
145
+ // const data = this.tokenStore.get(check);
146
+ // if(!data) callback(new Error("⚠️ OTP not found or expired"));
147
+
148
+ // if(Date.now() > data.expiresAt){
149
+ // this.tokenStore.delete(check);
150
+ // callback(new Error("⚠️ OTP expired"));
151
+ // }
152
+
153
+ // if(this.maxAttempt <= data.attempts){
154
+ // callback(new Error("⚠️ Max attempts reached"));
155
+ // }
156
+
157
+ // if(data.code != code){
158
+ // data.attempts+=1;
159
+ // this.tokenStore.set(check, data);
160
+ // callback(new Error("⚠️ Invalid OTP"));
161
+ // }
162
+
163
+ // this.tokenStore.delete(check);
164
+ // return true;
165
+ // }else if(this.storeType == 'redis'){
166
+ // callback(new Error('⚠️ Redis requires async/await. Use Promise style.'));
167
+ // }else{
168
+ // callback(new Error("❌ {storeTokens} should be 'memory' or 'redis'"));
169
+ // }
170
+ // }
171
+
172
+ // try {
173
+ // if(this.storeType == 'memory'){
174
+ // const data = await this.tokenStore.get(check);
175
+ // if(!data) throw new Error("⚠️ OTP not found or expired");
176
+
177
+ // if(Date.now() > data.expiresAt){
178
+ // this.tokenStore.delete(check);
179
+ // console.log("Now:", Date.now(), "ExpiresAt:", data.expiresAt);
180
+ // throw new Error("⚠️ OTP expired");
181
+ // }
182
+
183
+ // if(this.maxAttempt <= data.attempts){
184
+ // throw new Error("⚠️ Max attempts reached");
185
+ // }
186
+
187
+ // if(data.code != code){
188
+ // data.attempts+=1;
189
+ // this.tokenStore.set(check, data);
190
+ // throw new Error("⚠️ Invalid OTP");
191
+ // }
192
+
193
+ // this.tokenStore.delete(check);
194
+ // return true
195
+ // }else if(this.storeType == 'redis'){
196
+ // const rawData = await this.redis.get(check);
197
+ // if(!rawData) throw new Error("⚠️ OTP not found or expired");
198
+
199
+ // const data = JSON.parse(rawData);
200
+
201
+ // if (Date.now() > data.expiresAt) {
202
+ // await this.redis.del(check);
203
+ // throw new Error("⚠️ OTP expired");
204
+ // }
205
+
206
+ // if (data.attempts >= this.maxAttempts) {
207
+ // throw new Error("⚠️ Max attempts reached");
208
+ // }
209
+
210
+ // if (data.code !== code) {
211
+ // data.attempts++;
212
+ // await this.redis.set(check, JSON.stringify(data), 'EX', Math.floor((data.expiresAt - Date.now()) / 1000));
213
+ // throw new Error("⚠️ Invalid OTP");
214
+ // }
215
+
216
+ // await this.redis.del(check);
217
+ // return true;
218
+ // }else{
219
+ // throw new Error("❌ {storeTokens} should be 'memory' or 'redis'");
220
+ // }
221
+ // }catch(err){
222
+ // throw new Error(err);
223
+ // }
224
+ // }
225
+
226
+ setLogger(logger) {
227
+ if (typeof logger === "function") {
228
+ this.logger = logger;
229
+ } else if (logger && typeof logger.log === "function") {
230
+ this.logger = logger.log.bind(logger);
231
+ } else {
232
+ throw new Error("Logger must be a function or object with 'log' method");
233
+ }
234
+ console.log("✅ Logger attached");
235
+ return this;
236
+ }
237
+
238
+ logEvent(event, details) {
239
+ if (this.logger) {
240
+ this.logger({ event, timestamp: new Date().toISOString(), ...details });
241
+ }
242
+ }
243
+
244
+ // Put into OTPManager class (ensure nodemailer imported, tokenStore created earlier)
245
+ generate(length = 6, callback) {
246
+ try {
247
+ // generateSecureOTP should return a numeric string (e.g. "123456")
248
+ const code = generateSecureOTP(length, this.hashAlgorithm);
249
+ this.code = String(code); // always keep string
250
+ // callback-first style
251
+ if (typeof callback === 'function') {
252
+ callback(null, this.code);
253
+ return this;
254
+ }
255
+ // chainable
256
+ return this;
257
+ } catch (err) {
258
+ if (typeof callback === 'function') {
259
+ callback(err);
260
+ return this;
261
+ }
262
+ throw err;
263
+ }
264
+ }
265
+
266
+ async set(identifier, callback) {
267
+ const expiryMs = (typeof this.otpExpiry === 'number' ? this.otpExpiry : 300) * 1000; // ensure ms
268
+ const record = {
269
+ code: String(this.code),
270
+ to: identifier,
271
+ createdAt: Date.now(),
272
+ expiresAt: Date.now() + expiryMs,
273
+ attempts: 0,
274
+ cooldownUntil: Date.now() + this.cooldownTime
275
+ };
276
+
277
+ // Callback style for memory only
278
+ if (callback && typeof callback === 'function') {
279
+ try {
280
+ if (this.storeType === 'memory' || this.storeType === 'none') {
281
+ // store in memory map
282
+ if (!this.tokenStore) this.tokenStore = new Map();
283
+ this.tokenStore.set(identifier, record);
284
+ return callback(null, this); // return instance
285
+ } else if (this.storeType === 'redis') {
286
+ return callback(new Error('Redis requires async/await. Use Promise style.'));
287
+ } else {
288
+ return callback(new Error("{storeTokens} should be 'none', 'memory', or 'redis'"));
289
+ }
290
+ } catch (err) {
291
+ return callback(err);
292
+ }
293
+ }
294
+
295
+ // Promise/async style
296
+ if (this.storeType === 'memory' || this.storeType === 'none') {
297
+ if (!this.tokenStore) this.tokenStore = new Map();
298
+ this.tokenStore.set(identifier, record);
299
+ return this; // chainable
300
+ } else if (this.storeType === 'redis') {
301
+ const expirySeconds = Math.floor(expiryMs / 1000);
302
+ await this.redis.set(identifier, JSON.stringify(record), 'EX', expirySeconds);
303
+ return this;
304
+ } else {
305
+ throw new Error("{storeTokens} should be 'none', 'memory', or 'redis'");
306
+ }
307
+ }
308
+
309
+ async message(options, callback) {
310
+ const { to, subject, text, html, provider, apiKey, apiSecret } = options;
311
+
312
+ try {
313
+ // if developer provided their own sender function
314
+
315
+ if (!this.senderConfig)
316
+ throw new Error("Sender not configured. Use setSender() before message().");
317
+
318
+ // ---- EMAIL PART ----
319
+ if (this.senderConfig.via === 'email') {
320
+ let transporter;
321
+
322
+ if (this.senderConfig.service === 'gmail') {
323
+ transporter = nodemailer.createTransport({
324
+ service: 'gmail',
325
+ auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass },
326
+ });
327
+ } else if (this.senderConfig.service === 'smtp') {
328
+ transporter = nodemailer.createTransport({
329
+ host: this.senderConfig.host,
330
+ port: this.senderConfig.port,
331
+ secure: !!this.senderConfig.secure,
332
+ auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass },
333
+ });
334
+ } else {
335
+ throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
336
+ }
337
+
338
+ const mail = {
339
+ from: this.senderConfig.sender,
340
+ to,
341
+ subject: subject || 'Your OTP Code',
342
+ text: text || `Your OTP is ${this.code}`,
343
+ html: html || `<p>Your OTP is <b>${this.code}</b></p>`,
344
+ };
345
+
346
+ if (typeof callback === 'function') {
347
+ transporter.sendMail(mail, (err, info) => {
348
+ if (err) return callback(err);
349
+ this.recieverConfig = options;
350
+ return callback(null, info);
351
+ });
352
+ return; // stop here in callback mode
353
+ }
354
+
355
+ // Async/await version
356
+ const info = await transporter.sendMail(mail);
357
+ this.recieverConfig = options;
358
+ return info;
359
+ }
360
+
361
+ // ---- SMS PART ----
362
+ else if (this.senderConfig.via === 'sms') {
363
+ // Simple structure: no callback needed since SMS sending is async
364
+ const smsResponse = await sendSMS({
365
+ provider: provider || this.senderConfig.provider, // allow override
366
+ apiKey: apiKey || this.senderConfig.apiKey,
367
+ apiSecret: apiSecret || this.senderConfig.apiSecret,
368
+ from: this.senderConfig.sender,
369
+ to,
370
+ text: text || `Your OTP is ${this.code}`,
371
+ mock: this.senderConfig.mock ?? true,
372
+ });
373
+
374
+ this.recieverConfig = options;
375
+ return smsResponse;
376
+ }
377
+
378
+ // ---- TELEGRAM BOT PART ----
379
+ // else if(this.senderConfig.via === 'telegram'){
380
+ // const token = "8476429380:AAH8TNrdrqrzdoZ23ERy2w0eQnLSdUN8qB4";
381
+ // const bot = new TelegramBot(token, { polling: true });
382
+ // bot.onText(/\/start/, (msg) => {
383
+ // bot.sendMessage(msg.chat.id, "Please share your phone number:", {
384
+ // reply_markup: {
385
+ // keyboard: [
386
+ // [
387
+ // {
388
+ // text: "📞 Share my phone number",
389
+ // request_contact: true, // 🔥 this requests the user's phone
390
+ // },
391
+ // ],
392
+ // ],
393
+ // resize_keyboard: true,
394
+ // one_time_keyboard: true,
395
+ // },
396
+ // });
397
+ // });
398
+
399
+ // bot.on("polling_error", (err) => console.error("Polling error:", err));
400
+
401
+ // bot.on("contact", (msg) => {
402
+ // const phoneNumber = msg.contact.phone_number;
403
+ // const firstName = msg.contact.first_name;
404
+ // const userId = msg.from.id;
405
+
406
+ // console.log("User shared phone:", phoneNumber);
407
+
408
+ // // bot.sendMessage(
409
+ // // msg.chat.id,
410
+ // // `Thanks, ${firstName}! Your phone number is ${phoneNumber}.`
411
+ // // );
412
+
413
+ // // Here you can verify or store it in your database
414
+ // if(callback && typeof callback == 'function'){
415
+ // if(this.storeType == 'memory'){
416
+ // const data = this.tokenStore.get(phoneNumber);
417
+ // if(!data) callback(new Error('No OTP found for this phone number'));
418
+
419
+ // bot.sendMessage(msg.chat.id, `Your Verification code is <b>${this.code}</b>`, {parse_mode: "HTML"});
420
+ // callback(null);
421
+ // }else if(this.storeType == 'redis'){
422
+ // callback(new Error("Redis require async/await. Use promise style instead of callback"));
423
+ // }else{
424
+ // callback(new Error("{storeTokens} should be 'redis' or 'memory'"));
425
+ // }
426
+ // }else{
427
+ // try {
428
+ // if(this.storeType == 'memory'){
429
+ // const data = this.tokenStore.get(phoneNumber);
430
+ // if(!data) throw new Error('No OTP found for this phone number');
431
+
432
+ // bot.sendMessage(msg.chat.id, `Your Verification code is <b>${data.code}</b>`, {parse_mode: "HTML"});
433
+ // return null;
434
+ // }else if(this.storeType == 'redis'){
435
+ // const raw = await this.redis.get(phoneNumber);
436
+ // if(!raw) throw new Error('No OTP found for this phone number');
437
+
438
+ // const data = JSON.parse(raw);
439
+ // bot.sendMessage(msg.chat.id, `Your Verification code is <b>${data.code}</b>`, {parse_mode: "HTML"});
440
+ // }
441
+ // }catch(err){
442
+ // throw new Error(err);
443
+ // }
444
+ // }
445
+ // });
446
+ // }
447
+ else if (this.senderConfig.via === 'telegram') {
448
+ const token = this.senderConfig.token; // replace with your actual bot token
449
+ const bot = new TelegramBot(token, { polling: true });
450
+
451
+ // handle /start command
452
+ bot.onText(/\/start/, (msg) => {
453
+ const chatId = msg.chat.id;
454
+
455
+ bot.sendMessage(chatId, "Please share your phone number:", {
456
+ reply_markup: {
457
+ keyboard: [[
458
+ {
459
+ text: "📞 Share my phone number",
460
+ request_contact: true, // 🔥 asks user to send phone number
461
+ },
462
+ ]],
463
+ resize_keyboard: true,
464
+ one_time_keyboard: true,
465
+ },
466
+ });
467
+ });
468
+
469
+ // handle polling errors gracefully
470
+ bot.on("polling_error", (err) => console.error("Polling error:", err.message || err));
471
+
472
+ // handle user sending their phone number
473
+ bot.on("contact", async (msg) => {
474
+ const chatId = msg.chat.id;
475
+ const phoneNumber = msg.contact.phone_number;
476
+ const firstName = msg.contact.first_name;
477
+
478
+ console.log(`📞 ${firstName} shared phone number: ${phoneNumber}`);
479
+
480
+ try {
481
+ let data;
482
+
483
+ // 🔹 check OTP from memory or redis
484
+ if (this.storeType === "memory") {
485
+ data = this.tokenStore?.get(phoneNumber);
486
+ } else if (this.storeType === "redis") {
487
+ const raw = await this.redis.get(phoneNumber);
488
+ if (raw) data = JSON.parse(raw);
489
+ }
490
+
491
+ // 🔸 if no OTP found
492
+ if (!data || !data.code) {
493
+ bot.sendMessage(chatId, "❌ No OTP found for your number. Please request a new one.");
494
+ return;
495
+ }
496
+
497
+ // ✅ send OTP to Telegram
498
+ await bot.sendMessage(
499
+ chatId,
500
+ `✅ Your verification code is <b>${data.code}</b>`,
501
+ { parse_mode: "HTML" }
502
+ );
503
+
504
+ console.log(`✅ OTP ${data.code} sent to Telegram user: ${phoneNumber}`);
505
+ } catch (err) {
506
+ console.error("❌ Telegram OTP error:", err);
507
+ bot.sendMessage(chatId, "Something went wrong. Please try again.");
508
+ }
509
+ });
510
+ }
511
+ // ---- UNKNOWN METHOD ----
512
+ else {
513
+ throw new Error(`Unsupported sending method: ${this.senderConfig.via}`);
514
+ }
515
+
516
+ } catch (err) {
517
+ if (typeof callback === 'function') return callback(err);
518
+ throw err;
519
+ }
520
+ }
521
+
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
+ // 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");
543
+
544
+ if (Date.now() > data.expiresAt) {
545
+ this.tokenStore.delete(identifier);
546
+ throw new Error("OTP expired");
547
+ }
548
+
549
+ if ((this.maxAttempts || 5) <= data.attempts) {
550
+ throw new Error("Max attempts reached");
551
+ }
552
+
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
+ }
558
+
559
+ // success
560
+ this.tokenStore.delete(identifier);
561
+ return true;
562
+ }
563
+
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);
569
+
570
+ if (Date.now() > data.expiresAt) {
571
+ await this.redis.del(identifier);
572
+ throw new Error("OTP expired");
573
+ }
574
+
575
+ if ((this.maxAttempts || 5) <= (data.attempts || 0)) {
576
+ throw new Error("Max attempts reached");
577
+ }
578
+
579
+ if (String(data.code) !== String(code)) {
580
+ data.attempts = (data.attempts || 0) + 1;
581
+ const remaining = Math.max(0, Math.floor((data.expiresAt - Date.now()) / 1000));
582
+ await this.redis.set(identifier, JSON.stringify(data), 'EX', remaining);
583
+ throw new Error("Invalid OTP");
584
+ }
585
+
586
+ await this.redis.del(identifier);
587
+ return true;
588
+ }
589
+
590
+ throw new Error("{storeTokens} must be 'memory' or 'redis'");
591
+ }
592
+
593
+ cooldown(timestamp){
594
+ let timeforCooldown;
595
+ if(typeof timestamp == 'number') return timestamp;
596
+ if (typeof timestamp == 'string'){
597
+ let timeValue = parseInt(timestamp);
598
+ if(timestamp.endsWith('s')) return timeforCooldown = timeValue * 1000;
599
+ if(timestamp.endsWith('m')) return timeforCooldown = timeValue * 1000 * 60;
600
+ if(timestamp.endsWith('h')) return timeforCooldown = timeValue * 1000 * 60 * 60;
601
+ }
602
+
603
+ this.cooldownTime = timeforCooldown;
604
+ return this;
605
+ }
606
+
607
+ // async resend(identifier, callback){
608
+ // if(callback && typeof callback == 'function'){
609
+ // if(this.storeType == 'memory'){
610
+ // const data = this.tokenStore ? this.tokenStore.get(identifier) : null;
611
+ // if(!data){
612
+ // callback(new Error("No OTP generated yet."));
613
+ // }
614
+
615
+ // if(Date.now() <= data.cooldownUntil){
616
+ // let waitingTime = (data.cooldownUntil - Date.now())/1000;
617
+ // callback(new Error(`Cooling down is processing... You should wait ${waitingTime} seconds for sending OTP`));
618
+ // }else{
619
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
620
+ // // let transporter;
621
+ // // if (this.senderConfig.service === 'gmail') {
622
+ // // transporter = nodemailer.createTransport({
623
+ // // service: 'gmail',
624
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
625
+ // // });
626
+ // // } else if (this.senderConfig.service === 'smtp') {
627
+ // // transporter = nodemailer.createTransport({
628
+ // // host: this.senderConfig.host,
629
+ // // port: this.senderConfig.port,
630
+ // // secure: !!this.senderConfig.secure,
631
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
632
+ // // });
633
+ // // } else {
634
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
635
+ // // }
636
+ // // // prepare mail
637
+ // // const mail = {
638
+ // // from: this.senderConfig.sender,
639
+ // // to: this.recieverConfig.to,
640
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
641
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
642
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
643
+ // // };
644
+ // // transporter.sendMail(mail, (err, info)=>{
645
+ // // if(err) return callback(new Error(err));
646
+ // // callback(null, info);
647
+ // // });
648
+ // resendGeneratedOTP({
649
+ // from: this.senderConfig,
650
+ // to: this.recieverConfig.to,
651
+ // pass: this.senderConfig.pass,
652
+ // host: this.senderConfig.host,
653
+ // port: this.senderConfig.port,
654
+ // code: code,
655
+ // secure: this.senderConfig.secure ,
656
+ // subject: this.recieverConfig.subject,
657
+ // text: this.recieverConfig.text,
658
+ // html: this.recieverConfig.html
659
+ // }, (err, info)=>{
660
+ // if(err) return callback(new Error(err));
661
+ // callback(null, info);
662
+ // });
663
+ // data.cooldownUntil = null;
664
+ // data.code = code;
665
+ // this.tokenStore.set(identifier, data);
666
+ // }
667
+
668
+ // if(Date.now() > data.expiresAt){
669
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
670
+ // // let transporter;
671
+ // // if (this.senderConfig.service === 'gmail') {
672
+ // // transporter = nodemailer.createTransport({
673
+ // // service: 'gmail',
674
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
675
+ // // });
676
+ // // } else if (this.senderConfig.service === 'smtp') {
677
+ // // transporter = nodemailer.createTransport({
678
+ // // host: this.senderConfig.host,
679
+ // // port: this.senderConfig.port,
680
+ // // secure: !!this.senderConfig.secure,
681
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
682
+ // // });
683
+ // // } else {
684
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
685
+ // // }
686
+ // // // prepare mail
687
+ // // const mail = {
688
+ // // from: this.senderConfig.sender,
689
+ // // to: this.recieverConfig.to,
690
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
691
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
692
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
693
+ // // };
694
+ // // transporter.sendMail(mail, (err, info)=>{
695
+ // // if(err) return callback(new Error(err));
696
+ // // callback(null, info);
697
+ // // });
698
+ // resendGeneratedOTP({
699
+ // from: this.senderConfig,
700
+ // to: this.recieverConfig.to,
701
+ // pass: this.senderConfig.pass,
702
+ // host: this.senderConfig.host,
703
+ // port: this.senderConfig.port,
704
+ // code: code,
705
+ // secure: this.senderConfig.secure ,
706
+ // subject: this.recieverConfig.subject,
707
+ // text: this.recieverConfig.text,
708
+ // html: this.recieverConfig.html
709
+ // }, (err, info)=>{
710
+ // if(err) return callback(new Error(err));
711
+ // callback(null, info);
712
+ // });
713
+ // data.cooldownUntil = null;
714
+ // data.code = code;
715
+ // this.tokenStore.set(identifier, data);
716
+ // }
717
+
718
+ // }else if(this.storeType == 'redis'){
719
+ // callback(new Error(`Redis require async/await. Use promise style`));
720
+ // }else{
721
+ // callback(new Error("{storeTokens} should be 'none', 'memory', or 'redis'"));
722
+ // }
723
+ // }else{
724
+ // try{
725
+ // if(this.storeType == 'memory'){
726
+ // const data = this.tokenStore ? this.tokenStore.get(identifier) : null;
727
+ // if(!data){
728
+ // callback(new Error("No OTP generated yet."));
729
+ // }
730
+
731
+ // if(Date.now() <= data.cooldownUntil){
732
+ // let waitingTime = (data.cooldownUntil - Date.now())/1000;
733
+ // callback(new Error(`Cooling down is processing... You should wait ${waitingTime} seconds for sending OTP`));
734
+ // }else{
735
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
736
+ // // let transporter;
737
+ // // if (this.senderConfig.service === 'gmail') {
738
+ // // transporter = nodemailer.createTransport({
739
+ // // service: 'gmail',
740
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
741
+ // // });
742
+ // // } else if (this.senderConfig.service === 'smtp') {
743
+ // // transporter = nodemailer.createTransport({
744
+ // // host: this.senderConfig.host,
745
+ // // port: this.senderConfig.port,
746
+ // // secure: !!this.senderConfig.secure,
747
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
748
+ // // });
749
+ // // } else {
750
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
751
+ // // }
752
+ // // // prepare mail
753
+ // // const mail = {
754
+ // // from: this.senderConfig.sender,
755
+ // // to: this.recieverConfig.to,
756
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
757
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
758
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
759
+ // // };
760
+ // // await transporter.sendMail(mail);
761
+ // await resendGeneratedOTP({
762
+ // from: this.senderConfig,
763
+ // to: this.recieverConfig.to,
764
+ // pass: this.senderConfig.pass,
765
+ // host: this.senderConfig.host,
766
+ // port: this.senderConfig.port,
767
+ // code: code,
768
+ // secure: this.senderConfig.secure ,
769
+ // subject: this.recieverConfig.subject,
770
+ // text: this.recieverConfig.text,
771
+ // html: this.recieverConfig.html
772
+ // });
773
+ // data.cooldownUntil = 0;
774
+ // data.code = code;
775
+ // this.tokenStore.set(identifier, data);
776
+
777
+ // return null;
778
+ // }
779
+
780
+ // if(Date.now() > data.expiresAt){
781
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
782
+ // // let transporter;
783
+ // // if (this.senderConfig.service === 'gmail') {
784
+ // // transporter = nodemailer.createTransport({
785
+ // // service: 'gmail',
786
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
787
+ // // });
788
+ // // } else if (this.senderConfig.service === 'smtp') {
789
+ // // transporter = nodemailer.createTransport({
790
+ // // host: this.senderConfig.host,
791
+ // // port: this.senderConfig.port,
792
+ // // secure: !!this.senderConfig.secure,
793
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
794
+ // // });
795
+ // // } else {
796
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
797
+ // // }
798
+ // // // prepare mail
799
+ // // const mail = {
800
+ // // from: this.senderConfig.sender,
801
+ // // to: this.recieverConfig.to,
802
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
803
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
804
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
805
+ // // };
806
+ // // await transporter.sendMail(mail);
807
+ // await resendGeneratedOTP({
808
+ // from: this.senderConfig,
809
+ // to: this.recieverConfig.to,
810
+ // pass: this.senderConfig.pass,
811
+ // host: this.senderConfig.host,
812
+ // port: this.senderConfig.port,
813
+ // code: code,
814
+ // secure: this.senderConfig.secure ,
815
+ // subject: this.recieverConfig.subject,
816
+ // text: this.recieverConfig.text,
817
+ // html: this.recieverConfig.html
818
+ // });
819
+ // data.cooldownUntil = 0;
820
+ // data.code = code;
821
+ // this.tokenStore.set(identifier, data);
822
+
823
+ // return null;
824
+ // }
825
+ // }else if(this.storeType == 'redis'){
826
+ // const raw = await this.redis.get(identifier);
827
+ // if(!raw){
828
+ // throw new Error("No OTP generated yet.");
829
+ // }
830
+
831
+ // const data = JSON.parse(raw);
832
+
833
+ // if(Date.now() <= data.cooldownUntil){
834
+ // let waitingTime = (data.cooldownUntil - Date.now())/1000;
835
+ // throw new Error(`Cooling down is processing... You should wait ${waitingTime} seconds for sending OTP`);
836
+ // }else{
837
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
838
+ // // let transporter;
839
+ // // if (this.senderConfig.service === 'gmail') {
840
+ // // transporter = nodemailer.createTransport({
841
+ // // service: 'gmail',
842
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
843
+ // // });
844
+ // // } else if (this.senderConfig.service === 'smtp') {
845
+ // // transporter = nodemailer.createTransport({
846
+ // // host: this.senderConfig.host,
847
+ // // port: this.senderConfig.port,
848
+ // // secure: !!this.senderConfig.secure,
849
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
850
+ // // });
851
+ // // } else {
852
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
853
+ // // }
854
+ // // // prepare mail
855
+ // // const mail = {
856
+ // // from: this.senderConfig.sender,
857
+ // // to: this.recieverConfig.to,
858
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
859
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
860
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
861
+ // // };
862
+ // // await transporter.sendMail(mail);
863
+ // await resendGeneratedOTP({
864
+ // from: this.senderConfig,
865
+ // to: this.recieverConfig.to,
866
+ // pass: this.senderConfig.pass,
867
+ // host: this.senderConfig.host,
868
+ // port: this.senderConfig.port,
869
+ // code: code,
870
+ // secure: this.senderConfig.secure ,
871
+ // subject: this.recieverConfig.subject,
872
+ // text: this.recieverConfig.text,
873
+ // html: this.recieverConfig.html
874
+ // });
875
+ // data.cooldownUntil = 0;
876
+ // data.code = code;
877
+ // await this.redis.set(identifier, JSON.stringify(data));
878
+ // }
879
+
880
+ // if(Date.now() > data.expiresAt){
881
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
882
+ // // let transporter;
883
+ // // if (this.senderConfig.service === 'gmail') {
884
+ // // transporter = nodemailer.createTransport({
885
+ // // service: 'gmail',
886
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
887
+ // // });
888
+ // // } else if (this.senderConfig.service === 'smtp') {
889
+ // // transporter = nodemailer.createTransport({
890
+ // // host: this.senderConfig.host,
891
+ // // port: this.senderConfig.port,
892
+ // // secure: !!this.senderConfig.secure,
893
+ // // auth: { user: this.senderConfig.sender, pass: this.senderConfig.pass }
894
+ // // });
895
+ // // } else {
896
+ // // throw new Error(`Unsupported email service: ${this.senderConfig.service}`);
897
+ // // }
898
+ // // // prepare mail
899
+ // // const mail = {
900
+ // // from: this.senderConfig.sender,
901
+ // // to: this.recieverConfig.to,
902
+ // // subject: this.recieverConfig.subject || 'Your OTP Code',
903
+ // // text: this.recieverConfig.text || `Your OTP is ${code}`,
904
+ // // html: this.recieverConfig.html || `<p>Your OTP is <b>${code}</b></p>`
905
+ // // };
906
+ // // await transporter.sendMail(mail);
907
+ // await resendGeneratedOTP({
908
+ // from: this.senderConfig,
909
+ // to: this.recieverConfig.to,
910
+ // pass: this.senderConfig.pass,
911
+ // host: this.senderConfig.host,
912
+ // port: this.senderConfig.port,
913
+ // code: code,
914
+ // secure: this.senderConfig.secure ,
915
+ // subject: this.recieverConfig.subject,
916
+ // text: this.recieverConfig.text,
917
+ // html: this.recieverConfig.html
918
+ // });
919
+ // data.cooldownUntil = 0;
920
+ // data.code = code;
921
+ // await this.redis.set(identifier, JSON.stringify(data));
922
+ // }
923
+ // }
924
+ // }catch(err){
925
+ // throw new Error(err);
926
+ // }
927
+ // }
928
+
929
+ // }
930
+
931
+ async resend(identifier, callback) {
932
+ const handleError = (err) => {
933
+ if (callback && typeof callback === 'function') return callback(err);
934
+ throw err;
935
+ };
936
+
937
+ try {
938
+ let data;
939
+
940
+ // Get data from memory or Redis
941
+ if (this.storeType === 'memory') {
942
+ data = this.tokenStore.get(identifier);
943
+ } else if (this.storeType === 'redis') {
944
+ const raw = await this.redis.get(identifier);
945
+ data = raw ? JSON.parse(raw) : null;
946
+ } else {
947
+ return handleError(new Error("{storeTokens} should be 'memory' or 'redis'"));
948
+ }
949
+
950
+ if (!data) return handleError(new Error("No OTP generated yet."));
951
+
952
+ const now = Date.now();
953
+
954
+ // Check cooldown
955
+ if (data.cooldownUntil && now < data.cooldownUntil) {
956
+ const waitingTime = Math.ceil((data.cooldownUntil - now) / 1000);
957
+ return handleError(new Error(`Cooldown active. Wait ${waitingTime} seconds before resending OTP.`));
958
+ }
959
+
960
+ // Check if OTP expired
961
+ if (data.expiresAt && now > data.expiresAt) {
962
+ data.code = generateSecureOTP(data.code.length, this.hashAlgorithm);
963
+ data.expiresAt = now + this.otpExpiry;
964
+ } else {
965
+ // regenerate code to resend
966
+ data.code = generateSecureOTP(data.code.length, this.hashAlgorithm);
967
+ }
968
+
969
+ // Update cooldown
970
+ data.cooldownUntil = now + (this.cooldown || 30000); // default 30s
971
+
972
+ // Save updated data
973
+ if (this.storeType === 'memory') {
974
+ this.tokenStore.set(identifier, data);
975
+ } else if (this.storeType === 'redis') {
976
+ const ttlSeconds = Math.max(1, Math.ceil((data.expiresAt - now) / 1000));
977
+ await this.redis.set(identifier, JSON.stringify(data), "PX", ttlSeconds);
978
+ }
979
+
980
+ // Send OTP
981
+ const sendParams = {
982
+ from: this.senderConfig.sender,
983
+ to: data.to,
984
+ pass: this.senderConfig.pass,
985
+ host: this.senderConfig.host,
986
+ port: this.senderConfig.port,
987
+ secure: this.senderConfig.secure,
988
+ service: this.senderConfig.service,
989
+ subject: this.recieverConfig.subject || `Email verification`,
990
+ text: this.recieverConfig.text || `Your OTP is ${data.code}`,
991
+ html: this.recieverConfig.html || `<p>Your OTP is <b>${data.code}</b></p>`,
992
+ code: data.code
993
+ };
994
+
995
+ if (callback && typeof callback === 'function') {
996
+ resendGeneratedOTP(sendParams, callback);
997
+ } else {
998
+ await resendGeneratedOTP(sendParams);
999
+ return data.code;
1000
+ }
1001
+
1002
+ } catch (err) {
1003
+ handleError(err);
1004
+ }
1005
+ }
1006
+
1007
+ async setupTelegramBot(botToken) {
1008
+ const TelegramBot = require("node-telegram-bot-api");
1009
+ const bot = new TelegramBot(botToken, { polling: true });
1010
+
1011
+ bot.onText(/\/start/, async (msg) => {
1012
+ await bot.sendMessage(msg.chat.id, "📱 Please share your phone number:", {
1013
+ reply_markup: {
1014
+ keyboard: [[{ text: "Share my phone number", request_contact: true }]],
1015
+ resize_keyboard: true,
1016
+ one_time_keyboard: true,
1017
+ },
1018
+ });
1019
+ });
1020
+
1021
+ bot.on("contact", async (msg) => {
1022
+ const phone = msg.contact.phone_number;
1023
+ const chatId = msg.chat.id;
1024
+
1025
+ try {
1026
+ let record;
1027
+ if (this.storeType === "memory") {
1028
+ record = this.tokenStore.get(phone);
1029
+ } else if (this.storeType === "redis") {
1030
+ const raw = await this.redis.get(phone);
1031
+ record = raw ? JSON.parse(raw) : null;
1032
+ }
1033
+
1034
+ if (!record) {
1035
+ await bot.sendMessage(chatId, "❌ No OTP found for your number. Try again.");
1036
+ return;
1037
+ }
1038
+
1039
+ await bot.sendMessage(chatId, `✅ Your verification code: <b>${record.code}</b>`, { parse_mode: "HTML" });
1040
+ } catch (err) {
1041
+ console.error("Telegram error:", err);
1042
+ await bot.sendMessage(chatId, "⚠️ Something went wrong. Try later.");
1043
+ }
1044
+ });
1045
+
1046
+ console.log("🚀 Telegram verification bot ready!");
1047
+ }
1048
+
1049
+
1050
+ }
1051
+
1052
+ module.exports = OTPManager;