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.
- package/index.js +79 -175
- package/package.json +10 -4
- package/readme.md +247 -70
- package/src/helpers/helper.js +284 -0
- package/src/jwt/cookie/index.js +29 -0
- package/src/jwt/index.js +392 -0
- package/src/otp/index.js +1052 -0
- package/src/session/index.js +81 -0
- package/test.js +178 -31
- package/auth-verify.js +0 -182
- package/authverify.db +0 -0
- package/otp.db +0 -0
package/src/otp/index.js
ADDED
|
@@ -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;
|