auth-verify 0.0.2 → 1.0.3

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,284 @@
1
+ const crypto = require('crypto');
2
+ const nodemailer = require("nodemailer");
3
+ const axios = require('axios');
4
+
5
+ /**
6
+ * Convert time strings like "1h", "30m", "10s" to milliseconds
7
+ */
8
+ function parseTime(str) {
9
+ if (typeof str === 'number') return str; // already ms
10
+ if (typeof str !== 'string') return 0;
11
+
12
+ const num = parseInt(str);
13
+ if (str.endsWith('h')) return num * 60 * 60 * 1000;
14
+ if (str.endsWith('m')) return num * 60 * 1000;
15
+ if (str.endsWith('s')) return num * 1000;
16
+ return num;
17
+ }
18
+
19
+ /**
20
+ * Generate a random numeric OTP of given length
21
+ */
22
+ function generateSecureOTP(length = 6, hash = 'sha256') {
23
+ // Create random bytes
24
+ const randomBytes = crypto.randomBytes(32);
25
+ // Hash the bytes
26
+ const hashed = crypto.createHash(hash).update(randomBytes).digest('hex');
27
+
28
+ // Convert to numeric OTP
29
+ let otp = '';
30
+ for (let i = 0; i < hashed.length && otp.length < length; i++) {
31
+ const char = hashed[i];
32
+ if (!isNaN(char)) otp += char;
33
+ }
34
+
35
+ // If not enough digits, recursively generate more
36
+ if (otp.length < length) return generateSecureOTP(length, hash);
37
+ return otp.slice(0, length);
38
+ }
39
+
40
+ function otpSendingProcessByEmail({senderService, senderEmail, senderPass, senderHost, senderPort, senderSecure, receiverEmail, senderText, senderSubject, senderHtml}){
41
+ if(senderService == 'gmail'){
42
+ const transporter = nodemailer.createTransport({
43
+ service: senderService,
44
+ auth: {
45
+ user: senderEmail,
46
+ pass: senderPass, // not your normal password
47
+ },
48
+ });
49
+
50
+ if(senderText && !senderHtml){
51
+ transporter.sendMail({
52
+ from: senderEmail,
53
+ to: receiverEmail,
54
+ subject: senderSubject,
55
+ text: senderText,
56
+ // html: senderHtml
57
+ }, (err, info) => {
58
+ if (err) return console.error("❌ Failed:", err);
59
+ // console.log("✅ Email sent:", info.response);
60
+ });
61
+ }else if (senderHtml && !senderText){
62
+ transporter.sendMail({
63
+ from: senderEmail,
64
+ to: receiverEmail,
65
+ subject: senderSubject,
66
+ // text: senderText,
67
+ html: senderHtml
68
+ }, (err, info) => {
69
+ if (err) return console.error("❌ Failed:", err);
70
+ // console.log("✅ Email sent:", info.response);
71
+ });
72
+ }
73
+ }else{
74
+ const transporter = nodemailer.createTransport({
75
+ host: senderHost,
76
+ port: senderPort,
77
+ secure: senderSecure, // true for 465, false for others
78
+ auth: {
79
+ user: senderEmail,
80
+ pass: senderPass,
81
+ },
82
+ });
83
+
84
+ if(senderText && !senderHtml){
85
+ transporter.sendMail({
86
+ from: senderEmail,
87
+ to: receiverEmail,
88
+ subject: senderSubject,
89
+ text: senderText,
90
+ // html: senderHtml
91
+ }, (err, info) => {
92
+ if (err) return console.error("❌ Failed:", err);
93
+ // console.log("✅ Email sent:", info.response);
94
+ });
95
+ }else if (senderHtml && !senderText){
96
+ transporter.sendMail({
97
+ from: senderEmail,
98
+ to: receiverEmail,
99
+ subject: senderSubject,
100
+ // text: senderText,
101
+ html: senderHtml
102
+ }, (err, info) => {
103
+ if (err) return console.error("❌ Failed:", err);
104
+ // console.log("✅ Email sent:", info.response);
105
+ });
106
+ }
107
+ }
108
+ }
109
+
110
+ async function resendGeneratedOTP({from, to, pass, service, host, secure, port, subject, text, html, code}, callbackData){
111
+ if(callbackData && typeof callbackData == 'function'){
112
+ // const code = generateSecureOTP(data.code.length, this.hashAlgorithm);
113
+ let transporter;
114
+ if (service === 'gmail') {
115
+ transporter = nodemailer.createTransport({
116
+ service: 'gmail',
117
+ auth: { user: from, pass }
118
+ });
119
+ } else if (service === 'smtp') {
120
+ transporter = nodemailer.createTransport({
121
+ host,
122
+ port,
123
+ secure: !!secure,
124
+ auth: { user: from, pass }
125
+ });
126
+ } else {
127
+ throw new Error(`Unsupported email service: ${service}`);
128
+ }
129
+ // prepare mail
130
+ const mail = {
131
+ from,
132
+ to,
133
+ subject: subject || 'Your OTP Code',
134
+ text: text || `Your OTP is ${code}`,
135
+ html: html || `<p>Your OTP is <b>${code}</b></p>`
136
+ };
137
+ transporter.sendMail(mail, (err, info)=>{
138
+ if(err) return callbackData(new Error(err));
139
+ callbackData(null, info);
140
+ });
141
+ }else{
142
+ try{
143
+ let transporter;
144
+ if (service === 'gmail') {
145
+ transporter = nodemailer.createTransport({
146
+ service: 'gmail',
147
+ auth: { user: from, pass }
148
+ });
149
+ } else if (service === 'smtp') {
150
+ transporter = nodemailer.createTransport({
151
+ host,
152
+ port,
153
+ secure: !!secure,
154
+ auth: { user: from, pass: pass }
155
+ });
156
+ } else {
157
+ throw new Error(`Unsupported email service: ${service}`);
158
+ }
159
+ // prepare mail
160
+ const mail = {
161
+ from,
162
+ to,
163
+ subject: subject || 'Your OTP Code',
164
+ text: text || `Your OTP is ${code}`,
165
+ html: html || `<p>Your OTP is <b>${code}</b></p>`
166
+ };
167
+ await transporter.sendMail(mail);
168
+ }catch(err){
169
+ throw new Error(err);
170
+ }
171
+ }
172
+ }
173
+
174
+ async function sendSMS({ provider, apiKey, apiSecret, from, to, text, mock = false }) {
175
+ try {
176
+ // 🧪 For testing only — no real SMS sent
177
+ if (mock) {
178
+ console.log(`📱 [Mock SMS via ${provider}]`);
179
+ console.log(`→ To: ${to}`);
180
+ console.log(`→ Message: ${text}`);
181
+ return { status: "SENT (mock)", to, text };
182
+ }
183
+
184
+ // 🔷 Infobip API
185
+ if (provider === 'infobip') {
186
+ await axios.post(
187
+ 'https://api.infobip.com/sms/2/text/advanced',
188
+ {
189
+ messages: [{ from, destinations: [{ to }], text }],
190
+ },
191
+ {
192
+ headers: {
193
+ Authorization: `App ${apiKey}`,
194
+ 'Content-Type': 'application/json',
195
+ },
196
+ }
197
+ );
198
+ return { status: "SENT", provider: "Infobip", to };
199
+ }
200
+
201
+ // 🟪 Vonage API
202
+ if (provider === 'vonage') {
203
+ await axios.post(
204
+ 'https://rest.nexmo.com/sms/json',
205
+ {
206
+ api_key: apiKey,
207
+ api_secret: apiSecret,
208
+ to,
209
+ from,
210
+ text,
211
+ },
212
+ { headers: { 'Content-Type': 'application/json' } }
213
+ );
214
+ return { status: "SENT", provider: "Vonage", to };
215
+ }
216
+
217
+ // 🟩 Twilio API
218
+ if (provider === 'twilio') {
219
+ await axios.post(
220
+ `https://api.twilio.com/2010-04-01/Accounts/${apiKey}/Messages.json`,
221
+ new URLSearchParams({
222
+ To: to,
223
+ From: from,
224
+ Body: text,
225
+ }),
226
+ {
227
+ auth: {
228
+ username: apiKey,
229
+ password: apiSecret,
230
+ },
231
+ }
232
+ );
233
+ return { status: "SENT", provider: "Twilio", to };
234
+ }
235
+
236
+ throw new Error(`Unsupported SMS provider: ${provider}`);
237
+ } catch (err) {
238
+ console.error("❌ SMS send failed:", err.message);
239
+ throw new Error(err.message);
240
+ }
241
+ }
242
+
243
+ // async function sendOTPwithTelegramBot(otpCode){
244
+ // bot.onText(/\/start/, (msg) => {
245
+ // bot.sendMessage(chatId, "Please share your phone number:", {
246
+ // reply_markup: {
247
+ // keyboard: [
248
+ // [
249
+ // {
250
+ // text: "📞 Share my phone number",
251
+ // request_contact: true, // 🔥 this requests the user's phone
252
+ // },
253
+ // ],
254
+ // ],
255
+ // resize_keyboard: true,
256
+ // one_time_keyboard: true,
257
+ // },
258
+ // });
259
+ // bot.sendMessage(msg.chat.id, `Your Verification code is <b>${otpCode}</b>`, {parse_mode: "HTML"});
260
+ // });
261
+
262
+ // bot.on("contact", (msg) => {
263
+ // const phoneNumber = msg.contact.phone_number;
264
+ // const firstName = msg.contact.first_name;
265
+ // const userId = msg.from.id;
266
+
267
+ // console.log("User shared phone:", phoneNumber);
268
+
269
+ // bot.sendMessage(
270
+ // msg.chat.id,
271
+ // `Thanks, ${firstName}! Your phone number is ${phoneNumber}.`
272
+ // );
273
+
274
+ // // Here you can verify or store it in your database
275
+ // });
276
+ // }
277
+
278
+ module.exports = {
279
+ parseTime,
280
+ generateSecureOTP,
281
+ otpSendingProcessByEmail,
282
+ resendGeneratedOTP,
283
+ sendSMS
284
+ };
@@ -0,0 +1,208 @@
1
+ const jwt = require("jsonwebtoken");
2
+ const Redis = require("ioredis");
3
+ const { parseTime } = require('../helpers/helper');
4
+
5
+ class JWTManager {
6
+ constructor(secret, options = {}) {
7
+ if (!secret) throw new Error("JWT secret is required");
8
+ this.secret = secret;
9
+ this.storeType = options.storeTokens || "none";
10
+
11
+ if (this.storeType !== 'none') {
12
+ if (this.storeType === 'memory') {
13
+ this.tokenStore = new Map();
14
+ } else if (this.storeType === 'redis') {
15
+ this.redis = new Redis(options.redisUrl || "redis://localhost:6379");
16
+ } else {
17
+ throw new Error("{storeTokens} should be 'none', 'memory', or 'redis'");
18
+ }
19
+ }
20
+ }
21
+
22
+ // Helper: convert expiry string like "1h" / "30m" / "10s" to milliseconds
23
+ _parseExpiry(expiry) {
24
+ if (typeof expiry === 'number') return expiry;
25
+ if (typeof expiry !== 'string') return 3600000; // default 1h
26
+
27
+ const num = parseInt(expiry);
28
+ if (expiry.endsWith('h')) return num * 60 * 60 * 1000;
29
+ if (expiry.endsWith('m')) return num * 60 * 1000;
30
+ if (expiry.endsWith('s')) return num * 1000;
31
+ return num;
32
+ }
33
+
34
+ async sign(payload, expiry, callback) {
35
+ if (typeof expiry === 'function') {
36
+ callback = expiry;
37
+ expiry = '1h'; // default
38
+ }
39
+
40
+ const expiryMs = this._parseExpiry(expiry || '1h'); // fallback to '1h'
41
+ const expiryJwt = typeof expiry === 'number'
42
+ ? Math.floor(expiry / 1000)
43
+ : (typeof expiry === 'string' ? expiry : '1h');
44
+
45
+ // Callback version
46
+ if(callback && typeof callback === 'function') {
47
+ if(this.storeType === 'redis') return callback(new Error("⚠️ Redis requires async/await use promise style functions"));
48
+
49
+ jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, token) => {
50
+ if(err) return callback(err);
51
+
52
+ if(this.storeType === 'memory') {
53
+ this.tokenStore.set(token, {payload, createdAt: Date.now()});
54
+ setTimeout(() => this.tokenStore.delete(token), expiryMs);
55
+ }
56
+
57
+ callback(null, token);
58
+ });
59
+ return;
60
+ }
61
+
62
+ // Async version
63
+ const token = await new Promise((res, rej) => {
64
+ jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, t) => {
65
+ if(err) rej(err);
66
+ else res(t);
67
+ });
68
+ });
69
+
70
+ if(this.storeType === 'memory') {
71
+ this.tokenStore.set(token, {payload, createdAt: Date.now()});
72
+ setTimeout(() => this.tokenStore.delete(token), expiryMs);
73
+ } else if(this.storeType === 'redis') {
74
+ await this.redis.set(token, JSON.stringify({payload, createdAt: Date.now()}), "EX", Math.floor(expiryMs/1000));
75
+ }
76
+
77
+ return token;
78
+ }
79
+
80
+ async verify(token, callback) {
81
+ // Callback version
82
+ if (callback && typeof callback === 'function') {
83
+ if (this.storeType === 'redis') {
84
+ return callback(new Error("⚠️ Redis requires async/await. Use Promise style."));
85
+ }
86
+
87
+ if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
88
+ return callback(new Error("❌ Token not found or revoked"));
89
+ }
90
+
91
+ jwt.verify(token, this.secret, (err, decoded) => {
92
+ if (err) {
93
+ if (err.name === "TokenExpiredError") return callback(new Error("❌ Token expired!"));
94
+ if (err.name === "JsonWebTokenError") return callback(new Error("❌ Invalid token!"));
95
+ return callback(new Error("❌ JWT error: " + err.message));
96
+ }
97
+ callback(null, decoded);
98
+ });
99
+ return;
100
+ }
101
+
102
+ // Async / Promise version
103
+ try {
104
+ if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
105
+ throw new Error("❌ Token not found or revoked");
106
+ }
107
+
108
+ if (this.storeType === 'redis') {
109
+ const data = await this.redis.get(token);
110
+ if (!data) throw new Error("❌ Token not found or revoked");
111
+ }
112
+
113
+ const decoded = jwt.verify(token, this.secret);
114
+ return decoded;
115
+ } catch (err) {
116
+ throw new Error("❌ JWT verification failed: " + err.message);
117
+ }
118
+ }
119
+
120
+ async decode(token, callback) {
121
+ try {
122
+ const decoded = jwt.decode(token);
123
+ // if (callback && typeof callback === 'function') return callback(null, decoded);
124
+ return decoded;
125
+ } catch (err) {
126
+ // if (callback && typeof callback === 'function') return callback(err);
127
+ throw err;
128
+ }
129
+ }
130
+
131
+ // Optional: manual token revoke for memory/redis
132
+ async revoke(token, revokeTime = 0) {
133
+ // function parseTime(str) {
134
+ // if (typeof str === 'number') return str; // already in ms
135
+ // if (typeof str !== 'string') return 0;
136
+
137
+ // const num = parseInt(str);
138
+ // if (str.endsWith('h')) return num * 60 * 60 * 1000;
139
+ // if (str.endsWith('m')) return num * 60 * 1000;
140
+ // if (str.endsWith('s')) return num * 1000;
141
+ // return num;
142
+ // }
143
+
144
+ const revokeMs = parseTime(revokeTime);
145
+ const revokeAfter = Date.now() + revokeMs;
146
+
147
+
148
+ if (this.storeType === 'memory') {
149
+ if(revokeTime == 0){
150
+ this.tokenStore.delete(token);
151
+ }else{
152
+ setTimeout(() => this.tokenStore.delete(token), revokeAfter);
153
+ }
154
+ }else if(this.storeType == 'redis'){
155
+ if(revokeTime == 0){
156
+ await this.redis.del(token);
157
+ }else{
158
+ await this.redis.pexpire(token, revokeAfter);
159
+ }
160
+ }else{
161
+ throw new Error("❌ {storeTokens} should be 'memory' or 'redis' or 'none'");
162
+ }
163
+ }
164
+
165
+ async isRevoked(token) {
166
+ if(this.storeType === 'memory') {
167
+ const data = this.tokenStore.get(token);
168
+ return data?.revokedUntil && data.revokedUntil > Date.now();
169
+ } else if(this.storeType === 'redis') {
170
+ const dataRaw = await this.redis.get(token);
171
+ if (!dataRaw) return true;
172
+ const data = JSON.parse(dataRaw);
173
+ return data?.revokedUntil && data.revokedUntil > Date.now();
174
+ } else {
175
+ return false; // no store, can't revoke
176
+ }
177
+ }
178
+
179
+ async revokeUntil(token, timestamp){
180
+ // function parseTime(str) {
181
+ // if (typeof str === 'number') return str; // already in ms
182
+ // if (typeof str !== 'string') return 0;
183
+
184
+ // const num = parseInt(str);
185
+ // if (str.endsWith('h')) return num * 60 * 60 * 1000;
186
+ // if (str.endsWith('m')) return num * 60 * 1000;
187
+ // if (str.endsWith('s')) return num * 1000;
188
+ // return num;
189
+ // }
190
+
191
+ const revokeMs = parseTime(timestamp);
192
+ const revokedUntil = Date.now() + revokeMs;
193
+
194
+ if(this.storeType == 'memory'){
195
+ const data = this.tokenStore.get(token) || {};
196
+ this.tokenStore.set(token, {...data, revokedUntil: revokedUntil});
197
+ }else if(this.storeType == 'redis'){
198
+ const dataRaw = await this.redis.get(token);
199
+ const data = dataRaw ? JSON.parse(dataRaw) : {};
200
+ data.revokedUntil = revokedUntil;
201
+ await this.redis.set(token, JSON.stringify(data));
202
+ }else{
203
+ throw new Error("{storeTokens} should be 'memory' or 'redis'");
204
+ }
205
+ }
206
+ }
207
+
208
+ module.exports = JWTManager;