nodebb-plugin-phone-verification 1.2.4 → 2.0.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/library.js +69 -90
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/static/lib/admin.js +14 -17
- package/static/lib/main.js +26 -47
- package/templates/admin/plugins/phone-verification.tpl +10 -23
- package/templates/admin/settings/phone-verification.tpl +10 -23
- package/test/helpers.test.js +1 -28
- package/test/phone-storage.test.js +29 -59
- package/test/verification.test.js +32 -29
package/library.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
-
const https = require('https');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const nconf = require.main.require('nconf');
|
|
5
6
|
|
|
6
7
|
// NodeBB modules
|
|
7
8
|
let db;
|
|
@@ -24,12 +25,12 @@ const IP_BLOCK_HOURS = 24;
|
|
|
24
25
|
|
|
25
26
|
// ==================== הגדרות ברירת מחדל ====================
|
|
26
27
|
const defaultSettings = {
|
|
27
|
-
|
|
28
|
+
// עודכן לכתובת הצינתוקים
|
|
29
|
+
voiceServerUrl: 'https://www.call2all.co.il/ym/api/RunTzintuk',
|
|
28
30
|
voiceServerApiKey: '',
|
|
29
31
|
voiceServerEnabled: false,
|
|
30
32
|
blockUnverifiedUsers: false,
|
|
31
|
-
|
|
32
|
-
voiceMessageTemplate: 'הקוד שלך לאתר {siteTitle} הוא {code} אני חוזר. הקוד הוא {code}'
|
|
33
|
+
// הוסרו הגדרות TTS שאינן רלוונטיות לצינתוק
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
// ==================== פונקציות עזר ====================
|
|
@@ -46,20 +47,10 @@ plugin.normalizePhone = function (phone) {
|
|
|
46
47
|
return phone.replace(/[-\s]/g, '');
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
plugin.generateVerificationCode = function () {
|
|
50
|
-
const randomBytes = crypto.randomBytes(3);
|
|
51
|
-
const number = randomBytes.readUIntBE(0, 3) % 1000000;
|
|
52
|
-
return number.toString().padStart(6, '0');
|
|
53
|
-
};
|
|
54
|
-
|
|
55
50
|
plugin.hashCode = function (code) {
|
|
56
51
|
return crypto.createHash('sha256').update(code).digest('hex');
|
|
57
52
|
};
|
|
58
53
|
|
|
59
|
-
plugin.formatCodeForSpeech = function (code) {
|
|
60
|
-
return code.split('').join(' ');
|
|
61
|
-
};
|
|
62
|
-
|
|
63
54
|
// ==================== בדיקת הרשאות ====================
|
|
64
55
|
|
|
65
56
|
plugin.checkPostingPermissions = async function (data) {
|
|
@@ -75,7 +66,8 @@ plugin.checkPostingPermissions = async function (data) {
|
|
|
75
66
|
const phoneData = await plugin.getUserPhone(uid);
|
|
76
67
|
if (!phoneData || !phoneData.phoneVerified) {
|
|
77
68
|
const userSlug = await User.getUserField(uid, 'userslug');
|
|
78
|
-
const
|
|
69
|
+
const relativePath = nconf.get('relative_path');
|
|
70
|
+
const editUrl = userSlug ? `${relativePath}/user/${userSlug}/edit` : `${relativePath}/user/me/edit`;
|
|
79
71
|
throw new Error(`חובה לאמת מספר טלפון כדי להמשיך את הפעילות בפורום.<br/>אנא גש ל<a href="${editUrl}">הגדרות הפרופיל שלך</a>.`);
|
|
80
72
|
}
|
|
81
73
|
return data;
|
|
@@ -94,63 +86,48 @@ plugin.checkVotingPermissions = async function (data) {
|
|
|
94
86
|
const phoneData = await plugin.getUserPhone(uid);
|
|
95
87
|
if (!phoneData || !phoneData.phoneVerified) {
|
|
96
88
|
const userSlug = await User.getUserField(uid, 'userslug');
|
|
97
|
-
const
|
|
89
|
+
const relativePath = nconf.get('relative_path');
|
|
90
|
+
const editUrl = userSlug ? `${relativePath}/user/${userSlug}/edit` : `${relativePath}/user/me/edit`;
|
|
98
91
|
throw new Error(`חובה לאמת מספר טלפון כדי להמשיך את הפעילות בפורום.<br/>אנא גש ל<a href="${editUrl}">הגדרות הפרופיל שלך</a>.`);
|
|
99
92
|
}
|
|
100
93
|
return data;
|
|
101
94
|
};
|
|
102
95
|
|
|
103
96
|
plugin.checkMessagingPermissions = async function (data) {
|
|
104
|
-
// --- תיקון: בדיקת כל הווריאציות האפשריות (כולל fromuid באותיות קטנות) ---
|
|
105
97
|
const uid = data.fromuid || data.fromUid || data.uid;
|
|
106
98
|
|
|
107
|
-
// לוג לבדיקה שזה עובד עכשיו
|
|
108
|
-
// console.log(`[Phone-Verify] Checking message from UID: ${uid}`);
|
|
109
|
-
|
|
110
|
-
// אם עדיין לא מצאנו מזהה משתמש, מחזירים את המידע כמו שהוא
|
|
111
99
|
if (!uid || parseInt(uid, 10) === 0) return data;
|
|
112
100
|
|
|
113
101
|
const settings = await plugin.getSettings();
|
|
114
|
-
// אם ההגדרות כבויות - משחררים
|
|
115
102
|
if (!settings.blockUnverifiedUsers) return data;
|
|
116
103
|
|
|
117
|
-
// מנהלים מורשים תמיד
|
|
118
104
|
const isAdmin = await User.isAdministrator(uid);
|
|
119
105
|
if (isAdmin) return data;
|
|
120
106
|
|
|
121
|
-
// משתמשים מאומתים מורשים תמיד
|
|
122
107
|
const phoneData = await plugin.getUserPhone(uid);
|
|
123
108
|
if (phoneData && phoneData.phoneVerified) {
|
|
124
109
|
return data;
|
|
125
110
|
}
|
|
126
111
|
|
|
127
|
-
// --- המשתמש לא מאומת -> בדיקת שאר המשתתפים ---
|
|
128
|
-
|
|
129
112
|
const Messaging = require.main.require('./src/messaging');
|
|
130
113
|
|
|
131
|
-
// וידוא שיש לנו roomId
|
|
132
114
|
if (!data.roomId) return data;
|
|
133
115
|
|
|
134
|
-
// שליפת המשתתפים בחדר
|
|
135
116
|
const roomUids = await Messaging.getUidsInRoom(data.roomId, 0, -1);
|
|
136
117
|
|
|
137
|
-
// סינון השולח עצמו
|
|
138
118
|
const targetUids = roomUids.filter(id => parseInt(id, 10) !== parseInt(uid, 10));
|
|
139
119
|
|
|
140
|
-
// הכנת הקישור לפרופיל
|
|
141
120
|
const userSlug = await User.getUserField(uid, 'userslug');
|
|
142
|
-
const
|
|
121
|
+
const relativePath = nconf.get('relative_path');
|
|
122
|
+
const editUrl = userSlug ? `${relativePath}/user/${userSlug}/edit` : `${relativePath}/user/me/edit`;
|
|
143
123
|
const errorMsg = `חובה לאמת מספר טלפון כדי לשלוח הודעות.<br/>אנא גש ל<a href="${editUrl}">הגדרות הפרופיל שלך</a>.`;
|
|
144
124
|
|
|
145
|
-
// אם אין אף אחד אחר בשיחה (מדבר לעצמו) - חוסמים
|
|
146
125
|
if (targetUids.length === 0) {
|
|
147
126
|
throw new Error(errorMsg);
|
|
148
127
|
}
|
|
149
128
|
|
|
150
|
-
// בדיקה שכל המשתתפים האחרים הם מנהלים
|
|
151
129
|
for (const targetUid of targetUids) {
|
|
152
130
|
const isTargetAdmin = await User.isAdministrator(targetUid);
|
|
153
|
-
// אם נמצא אפילו משתתף אחד שאינו מנהל -> חסימה
|
|
154
131
|
if (!isTargetAdmin) {
|
|
155
132
|
throw new Error(errorMsg);
|
|
156
133
|
}
|
|
@@ -159,44 +136,48 @@ plugin.checkMessagingPermissions = async function (data) {
|
|
|
159
136
|
return data;
|
|
160
137
|
};
|
|
161
138
|
|
|
162
|
-
// ==================== שליחת
|
|
139
|
+
// ==================== שליחת צינתוק (Tzintuk) ====================
|
|
163
140
|
|
|
164
|
-
plugin.
|
|
141
|
+
plugin.sendTzintuk = async function (phone) {
|
|
165
142
|
const settings = await plugin.getSettings();
|
|
166
|
-
if (!meta) meta = require.main.require('./src/meta');
|
|
167
|
-
const siteTitle = meta.config.title || 'האתר';
|
|
168
143
|
|
|
169
144
|
if (!settings.voiceServerEnabled || !settings.voiceServerApiKey) {
|
|
170
145
|
return { success: false, error: 'VOICE_SERVER_DISABLED', message: 'שרת השיחות לא מוגדר' };
|
|
171
146
|
}
|
|
172
147
|
|
|
173
148
|
try {
|
|
174
|
-
const spokenCode = plugin.formatCodeForSpeech(code);
|
|
175
|
-
let messageText = settings.voiceMessageTemplate || defaultSettings.voiceMessageTemplate;
|
|
176
|
-
messageText = messageText.replace(/{code}/g, spokenCode).replace(/{siteTitle}/g, siteTitle);
|
|
177
|
-
|
|
178
|
-
const phonesData = {};
|
|
179
|
-
phonesData[phone] = { name: 'משתמש', moreinfo: messageText, blocked: false };
|
|
180
|
-
|
|
181
149
|
const baseUrl = settings.voiceServerUrl || defaultSettings.voiceServerUrl;
|
|
150
|
+
|
|
151
|
+
// פרמטרים לצינתוק עם זיהוי רנדומלי
|
|
182
152
|
const params = new URLSearchParams({
|
|
183
|
-
|
|
184
|
-
phones:
|
|
185
|
-
|
|
153
|
+
token: settings.voiceServerApiKey,
|
|
154
|
+
phones: phone,
|
|
155
|
+
callerId: 'RAND', // חובה עבור קבלת קוד אימות אוטומטי
|
|
156
|
+
// tzintukTimeOut: '9' // אופציונלי
|
|
186
157
|
});
|
|
187
158
|
|
|
188
159
|
const url = `${baseUrl}?${params.toString()}`;
|
|
189
160
|
|
|
190
|
-
// עקיפת בעיות SSL (אופציונלי)
|
|
191
161
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
192
162
|
|
|
193
163
|
const response = await fetch(url, { method: 'GET', agent: agent });
|
|
194
164
|
|
|
195
165
|
if (!response.ok) return { success: false, error: 'VOICE_SERVER_ERROR', message: 'שגיאה בשרת השיחות' };
|
|
166
|
+
|
|
196
167
|
const result = await response.json();
|
|
197
168
|
|
|
198
|
-
|
|
199
|
-
|
|
169
|
+
// בדיקת תקינות התגובה וקיום verifyCode
|
|
170
|
+
if (result.responseStatus === 'OK' && result.verifyCode) {
|
|
171
|
+
return {
|
|
172
|
+
success: true,
|
|
173
|
+
code: result.verifyCode, // מחזירים את הקוד שהתקבל מהשרת
|
|
174
|
+
message: 'השיחה נשלחה בהצלחה'
|
|
175
|
+
};
|
|
176
|
+
} else if (result.errors && Object.keys(result.errors).length > 0) {
|
|
177
|
+
// טיפול בשגיאות ספציפיות
|
|
178
|
+
const errorKey = Object.keys(result.errors)[0];
|
|
179
|
+
const errorMsg = result.errors[errorKey];
|
|
180
|
+
return { success: false, error: 'API_ERROR', message: `שגיאה: ${errorMsg}` };
|
|
200
181
|
} else {
|
|
201
182
|
return { success: false, error: 'VOICE_SERVER_ERROR', message: result.message || 'שגיאה בשליחת השיחה' };
|
|
202
183
|
}
|
|
@@ -235,11 +216,11 @@ plugin.verifyCode = async function (phone, code) {
|
|
|
235
216
|
if (!db) return { success: false, error: 'DB_ERROR' };
|
|
236
217
|
const data = await db.getObject(key);
|
|
237
218
|
|
|
238
|
-
if (!data) return { success: false, error: 'CODE_NOT_FOUND', message: 'לא
|
|
219
|
+
if (!data) return { success: false, error: 'CODE_NOT_FOUND', message: 'לא נמצאה בקשת אימות' };
|
|
239
220
|
if (data.blockedUntil && parseInt(data.blockedUntil, 10) > now) {
|
|
240
221
|
return { success: false, error: 'PHONE_BLOCKED', message: 'המספר חסום זמנית' };
|
|
241
222
|
}
|
|
242
|
-
if (parseInt(data.expiresAt, 10) < now) return { success: false, error: 'CODE_EXPIRED', message: '
|
|
223
|
+
if (parseInt(data.expiresAt, 10) < now) return { success: false, error: 'CODE_EXPIRED', message: 'הזמן לאימות עבר' };
|
|
243
224
|
|
|
244
225
|
if (plugin.hashCode(code) === data.hashedCode) {
|
|
245
226
|
await db.delete(key);
|
|
@@ -300,7 +281,6 @@ plugin.clearVerifiedPhone = async function (phone) {
|
|
|
300
281
|
plugin.savePhoneToUser = async function (uid, phone, verified = true, forceOverride = false) {
|
|
301
282
|
if (!db || !User) return { success: false };
|
|
302
283
|
|
|
303
|
-
// 1. מקרה של אימות ללא טלפון
|
|
304
284
|
if (!phone) {
|
|
305
285
|
await User.setUserFields(uid, {
|
|
306
286
|
phoneVerified: verified ? 1 : 0,
|
|
@@ -317,25 +297,18 @@ plugin.savePhoneToUser = async function (uid, phone, verified = true, forceOverr
|
|
|
317
297
|
const normalizedPhone = plugin.normalizePhone(phone);
|
|
318
298
|
const existingUid = await db.sortedSetScore('phone:uid', normalizedPhone);
|
|
319
299
|
|
|
320
|
-
// 2. בדיקת כפילות
|
|
321
300
|
if (existingUid) {
|
|
322
|
-
// אם המספר שייך למשתמש אחר
|
|
323
301
|
if (parseInt(existingUid, 10) !== parseInt(uid, 10)) {
|
|
324
302
|
if (forceOverride) {
|
|
325
|
-
// === תיקון: דריסה בכוח (למנהלים) ===
|
|
326
|
-
// מחיקת המספר מהמשתמש הישן
|
|
327
303
|
console.log(`[phone-verification] Force overwriting phone ${normalizedPhone} from user ${existingUid} to ${uid}`);
|
|
328
304
|
|
|
329
|
-
// הסרה מה-Set של המשתמש הישן
|
|
330
305
|
await User.setUserFields(existingUid, {
|
|
331
306
|
[PHONE_FIELD_KEY]: '',
|
|
332
307
|
phoneVerified: 0,
|
|
333
308
|
phoneVerifiedAt: 0
|
|
334
309
|
});
|
|
335
310
|
await db.sortedSetRemove('users:phone', existingUid);
|
|
336
|
-
// (הערה: לא צריך להסיר מ-phone:uid כי אנחנו דורסים אותו מייד למטה)
|
|
337
311
|
} else {
|
|
338
|
-
// אם זה לא מנהל - זרוק שגיאה
|
|
339
312
|
return { success: false, error: 'PHONE_EXISTS', message: 'המספר כבר רשום למשתמש אחר' };
|
|
340
313
|
}
|
|
341
314
|
}
|
|
@@ -348,7 +321,6 @@ plugin.savePhoneToUser = async function (uid, phone, verified = true, forceOverr
|
|
|
348
321
|
phoneVerifiedAt: verified ? now : 0
|
|
349
322
|
});
|
|
350
323
|
|
|
351
|
-
// עדכון/דריסה של הרשומה ב-DB
|
|
352
324
|
await db.sortedSetAdd('phone:uid', uid, normalizedPhone);
|
|
353
325
|
await db.sortedSetAdd('users:phone', now, uid);
|
|
354
326
|
return { success: true };
|
|
@@ -406,19 +378,16 @@ plugin.checkRegistration = async function (data) {
|
|
|
406
378
|
const existingUid = await plugin.findUserByPhone(normalizedPhone);
|
|
407
379
|
|
|
408
380
|
if (existingUid) {
|
|
409
|
-
// אם המשתמש מחובר ומנסה לעדכן, או אם המספר תפוס על ידי מישהו אחר
|
|
410
381
|
if (!req.uid || parseInt(existingUid, 10) !== parseInt(req.uid, 10)) {
|
|
411
382
|
throw new Error('מספר הטלפון כבר רשום במערכת למשתמש אחר');
|
|
412
383
|
}
|
|
413
384
|
}
|
|
414
385
|
|
|
415
|
-
// אם הגעת לכאן, הנתונים תקינים.
|
|
416
|
-
// ב-Hook של checkRegistration, פשוט מחזירים את data כדי להמשיך ברישום.
|
|
417
386
|
return data;
|
|
418
387
|
|
|
419
388
|
} catch (err) {
|
|
420
389
|
console.error('[phone-verification] Registration check error:', err);
|
|
421
|
-
throw err;
|
|
390
|
+
throw err;
|
|
422
391
|
}
|
|
423
392
|
};
|
|
424
393
|
|
|
@@ -478,7 +447,6 @@ plugin.init = async function (params) {
|
|
|
478
447
|
// --- SOCKET.IO EVENTS ---
|
|
479
448
|
SocketPlugins.call2all = {};
|
|
480
449
|
|
|
481
|
-
// 1. פונקציה חדשה: מציאת משתמש לפי שם
|
|
482
450
|
SocketPlugins.call2all.getUidByUsername = async function (socket, data) {
|
|
483
451
|
if (!data || !data.username) throw new Error('נא לספק שם משתמש');
|
|
484
452
|
const uid = await User.getUidByUsername(data.username);
|
|
@@ -486,7 +454,6 @@ plugin.init = async function (params) {
|
|
|
486
454
|
return uid;
|
|
487
455
|
};
|
|
488
456
|
|
|
489
|
-
// 2. הוספת משתמש מאומת (מתוקן)
|
|
490
457
|
SocketPlugins.call2all.adminAddVerifiedUser = async function (socket, data) {
|
|
491
458
|
if (!data || !data.uid) throw new Error('חסר מזהה משתמש');
|
|
492
459
|
const isAdmin = await User.isAdministrator(socket.uid);
|
|
@@ -503,7 +470,6 @@ plugin.init = async function (params) {
|
|
|
503
470
|
if (!result.success) throw new Error(result.message);
|
|
504
471
|
};
|
|
505
472
|
|
|
506
|
-
// 3. אימות ידני
|
|
507
473
|
SocketPlugins.call2all.adminVerifyUser = async function (socket, data) {
|
|
508
474
|
if (!data || !data.uid) throw new Error('שגיאה');
|
|
509
475
|
const isAdmin = await User.isAdministrator(socket.uid);
|
|
@@ -513,7 +479,6 @@ plugin.init = async function (params) {
|
|
|
513
479
|
await db.sortedSetAdd('users:phone', Date.now(), data.uid);
|
|
514
480
|
};
|
|
515
481
|
|
|
516
|
-
// 4. ביטול אימות
|
|
517
482
|
SocketPlugins.call2all.adminUnverifyUser = async function (socket, data) {
|
|
518
483
|
if (!data || !data.uid) throw new Error('שגיאה');
|
|
519
484
|
const isAdmin = await User.isAdministrator(socket.uid);
|
|
@@ -522,7 +487,6 @@ plugin.init = async function (params) {
|
|
|
522
487
|
await User.setUserFields(data.uid, { phoneVerified: 0, phoneVerifiedAt: 0 });
|
|
523
488
|
};
|
|
524
489
|
|
|
525
|
-
// 5. מחיקת טלפון
|
|
526
490
|
SocketPlugins.call2all.adminDeleteUserPhone = async function (socket, data) {
|
|
527
491
|
if (!data || !data.uid) throw new Error('שגיאה');
|
|
528
492
|
const isAdmin = await User.isAdministrator(socket.uid);
|
|
@@ -573,22 +537,18 @@ plugin.getSettings = async function () {
|
|
|
573
537
|
return {
|
|
574
538
|
voiceServerUrl: settings.voiceServerUrl || defaultSettings.voiceServerUrl,
|
|
575
539
|
voiceServerApiKey: settings.voiceServerApiKey || '',
|
|
576
|
-
voiceServerEnabled: isTrue(settings.voiceServerEnabled),
|
|
577
|
-
blockUnverifiedUsers: isTrue(settings.blockUnverifiedUsers)
|
|
578
|
-
voiceTtsMode: settings.voiceTtsMode || '1',
|
|
579
|
-
voiceMessageTemplate: settings.voiceMessageTemplate || defaultSettings.voiceMessageTemplate
|
|
540
|
+
voiceServerEnabled: isTrue(settings.voiceServerEnabled),
|
|
541
|
+
blockUnverifiedUsers: isTrue(settings.blockUnverifiedUsers)
|
|
580
542
|
};
|
|
581
543
|
};
|
|
582
544
|
|
|
583
545
|
plugin.saveSettings = async function (settings) {
|
|
584
546
|
if (!meta) return false;
|
|
585
547
|
await meta.settings.set('phone-verification', {
|
|
586
|
-
voiceServerUrl: settings.voiceServerUrl ||
|
|
548
|
+
voiceServerUrl: settings.voiceServerUrl || defaultSettings.voiceServerUrl,
|
|
587
549
|
voiceServerApiKey: settings.voiceServerApiKey || '',
|
|
588
550
|
voiceServerEnabled: settings.voiceServerEnabled ? 'true' : 'false',
|
|
589
|
-
blockUnverifiedUsers: settings.blockUnverifiedUsers ? 'true' : 'false'
|
|
590
|
-
voiceTtsMode: settings.voiceTtsMode || '1',
|
|
591
|
-
voiceMessageTemplate: settings.voiceMessageTemplate || defaultSettings.voiceMessageTemplate
|
|
551
|
+
blockUnverifiedUsers: settings.blockUnverifiedUsers ? 'true' : 'false'
|
|
592
552
|
});
|
|
593
553
|
return true;
|
|
594
554
|
};
|
|
@@ -616,7 +576,9 @@ plugin.apiAdminTestCall = async function (req, res) {
|
|
|
616
576
|
try {
|
|
617
577
|
const { phoneNumber } = req.body;
|
|
618
578
|
if (!phoneNumber) return res.json({ success: false, message: 'חסר טלפון' });
|
|
619
|
-
|
|
579
|
+
|
|
580
|
+
// בצינתוק, השרת מחזיר את הקוד. בבדיקה אנו רק מוודאים שיצא שיחה
|
|
581
|
+
const result = await plugin.sendTzintuk(plugin.normalizePhone(phoneNumber));
|
|
620
582
|
res.json(result);
|
|
621
583
|
} catch (err) { res.json({ success: false }); }
|
|
622
584
|
};
|
|
@@ -635,17 +597,25 @@ plugin.apiSendCode = async function (req, res) {
|
|
|
635
597
|
if (!plugin.validatePhoneNumber(clean)) return res.json({ success: false, error: 'INVALID' });
|
|
636
598
|
|
|
637
599
|
const existingUid = await plugin.findUserByPhone(clean);
|
|
638
|
-
// בדיקת כפילות: אם המספר שייך למשתמש אחר
|
|
639
600
|
if (existingUid && (!req.uid || parseInt(existingUid) !== parseInt(req.uid))) {
|
|
640
601
|
return res.json({ success: false, error: 'EXISTS', message: 'המספר תפוס' });
|
|
641
602
|
}
|
|
642
603
|
|
|
643
|
-
|
|
644
|
-
await plugin.
|
|
645
|
-
const result = await plugin.sendVoiceCall(clean, code);
|
|
604
|
+
// --- שינוי: שליחת בקשה לשרת כדי לקבל את הקוד (Caller ID) ---
|
|
605
|
+
const result = await plugin.sendTzintuk(clean);
|
|
646
606
|
|
|
647
|
-
|
|
648
|
-
|
|
607
|
+
if (result.success && result.code) {
|
|
608
|
+
// שמירת הקוד שהתקבל מהשרת
|
|
609
|
+
await plugin.saveVerificationCode(clean, result.code);
|
|
610
|
+
res.json({ success: true, message: 'צינתוק נשלח, נא להזין את 4 הספרות האחרונות', method: 'tzintuk' });
|
|
611
|
+
} else {
|
|
612
|
+
res.json({ success: false, message: result.message || 'תקלה בשליחת הצינתוק' });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.error(err);
|
|
617
|
+
res.json({ success: false });
|
|
618
|
+
}
|
|
649
619
|
};
|
|
650
620
|
|
|
651
621
|
plugin.apiVerifyCode = async function (req, res) {
|
|
@@ -679,19 +649,28 @@ plugin.apiInitiateCall = async function (req, res) {
|
|
|
679
649
|
}
|
|
680
650
|
}
|
|
681
651
|
|
|
682
|
-
|
|
683
|
-
const
|
|
652
|
+
// --- שינוי: שליחת צינתוק במקום יצירת קוד מקומי ---
|
|
653
|
+
const result = await plugin.sendTzintuk(normalizedPhone);
|
|
654
|
+
|
|
655
|
+
if (!result.success || !result.code) {
|
|
656
|
+
return res.json(result);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const saveResult = await plugin.saveVerificationCode(normalizedPhone, result.code);
|
|
684
660
|
|
|
685
661
|
if (!saveResult.success) {
|
|
686
662
|
return res.json(saveResult);
|
|
687
663
|
}
|
|
688
664
|
|
|
689
|
-
|
|
665
|
+
// שימו לב: לא מחזירים את הקוד לקליינט בייצור (אלא אם רוצים דיבוג)
|
|
666
|
+
// כאן הקליינט צריך לבקש מהמשתמש את 4 הספרות האחרונות
|
|
667
|
+
res.json({ success: true, phone: normalizedPhone, expiresAt: saveResult.expiresAt });
|
|
690
668
|
|
|
691
669
|
} catch (err) {
|
|
692
670
|
res.json({ success: false, error: 'SERVER_ERROR', message: 'אירעה שגיאה' });
|
|
693
671
|
}
|
|
694
672
|
};
|
|
673
|
+
|
|
695
674
|
plugin.apiGetUserPhoneProfile = async function (req, res) {
|
|
696
675
|
try {
|
|
697
676
|
const uid = await User.getUidByUserslug(req.params.userslug);
|
|
@@ -790,4 +769,4 @@ plugin.userDelete = async function (data) {
|
|
|
790
769
|
} catch (e) {}
|
|
791
770
|
};
|
|
792
771
|
|
|
793
|
-
module.exports = plugin;
|
|
772
|
+
module.exports = plugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "nodebb-plugin-phone-verification",
|
|
3
3
|
"name": "Phone Verification",
|
|
4
4
|
"description": "אימות מספר טלפון נייד בתהליך ההרשמה לפורום ובפרופיל המשתמש",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.0",
|
|
6
6
|
"library": "./library.js",
|
|
7
7
|
"hooks": [
|
|
8
8
|
{ "hook": "filter:register.check", "method": "checkRegistration" },
|
package/static/lib/admin.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* globals $, app, socket, config */
|
|
4
3
|
|
|
5
4
|
define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], function(Settings, bootbox, alerts) {
|
|
6
5
|
var ACP = {};
|
|
7
6
|
|
|
8
7
|
ACP.init = function() {
|
|
9
|
-
// הגדרת משתנים בראש הפונקציה כדי שיהיו זמינים לכולם בתוכה
|
|
10
8
|
var usersTbody = $('#users-tbody');
|
|
11
9
|
var paginationUl = $('#users-pagination');
|
|
12
10
|
var currentPage = 1;
|
|
13
11
|
|
|
14
|
-
// 1. טעינת הגדרות
|
|
15
12
|
Settings.load('phone-verification', $('#voice-settings-form'));
|
|
16
13
|
|
|
17
14
|
$('#save-settings-btn').on('click', function(e) {
|
|
@@ -21,11 +18,10 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
21
18
|
});
|
|
22
19
|
});
|
|
23
20
|
|
|
24
|
-
// --- פונקציות עזר (הועברו פנימה כדי להכיר את המשתנים) ---
|
|
25
21
|
|
|
26
22
|
function renderPagination(curr, total) {
|
|
27
23
|
currentPage = curr;
|
|
28
|
-
paginationUl.empty();
|
|
24
|
+
paginationUl.empty();
|
|
29
25
|
if(total <= 1) return;
|
|
30
26
|
for(var i=1; i<=total; i++) {
|
|
31
27
|
var active = i === curr ? 'active' : '';
|
|
@@ -35,13 +31,11 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
35
31
|
|
|
36
32
|
function buildUserRow(user) {
|
|
37
33
|
var displayName = user.username || ('משתמש ' + user.uid);
|
|
38
|
-
// מניעת XSS בסיסית לשם המשתמש
|
|
39
34
|
var safeName = $('<div>').text(displayName).html();
|
|
40
|
-
var userLink = '/admin/manage/users?searchBy=uid&query=' + user.uid + '&page=1&sortBy=lastonline';
|
|
35
|
+
var userLink = config.relative_path + '/admin/manage/users?searchBy=uid&query=' + user.uid + '&page=1&sortBy=lastonline';
|
|
41
36
|
|
|
42
37
|
var statusBadge = user.phoneVerified ? '<span class="label label-success">מאומת</span>' : '<span class="label label-warning">ממתין</span>';
|
|
43
38
|
|
|
44
|
-
// מניעת XSS לטלפון
|
|
45
39
|
var safePhone = user.phone ? $('<div>').text(user.phone).html() : '<span class="text-muted">-- ללא --</span>';
|
|
46
40
|
var dateStr = user.phoneVerifiedAt ? new Date(user.phoneVerifiedAt).toLocaleDateString('he-IL') : '-';
|
|
47
41
|
|
|
@@ -65,7 +59,7 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
65
59
|
page = page || 1;
|
|
66
60
|
usersTbody.html('<tr><td colspan="6" class="text-center"><i class="fa fa-spinner fa-spin"></i> טוען נתונים...</td></tr>');
|
|
67
61
|
|
|
68
|
-
$.get('/api/admin/plugins/phone-verification/users', { page: page }, function(data) {
|
|
62
|
+
$.get(config.relative_path + '/api/admin/plugins/phone-verification/users', { page: page }, function(data) {
|
|
69
63
|
if (!data || !data.success) {
|
|
70
64
|
usersTbody.html('<tr><td colspan="6" class="text-center text-danger">שגיאה בטעינת נתונים</td></tr>');
|
|
71
65
|
return;
|
|
@@ -81,17 +75,14 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
81
75
|
});
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
// --- הפעלת ברירת מחדל ---
|
|
85
78
|
loadUsers(1);
|
|
86
79
|
|
|
87
|
-
// --- אירועים (Event Listeners) ---
|
|
88
80
|
|
|
89
81
|
paginationUl.on('click', 'a.page-link', function(e) {
|
|
90
82
|
e.preventDefault();
|
|
91
83
|
loadUsers($(this).data('page'));
|
|
92
84
|
});
|
|
93
85
|
|
|
94
|
-
// הוספה ידנית
|
|
95
86
|
$('#btn-add-manual-user').on('click', function() {
|
|
96
87
|
bootbox.prompt("הזן את <b>שם המשתמש</b> שברצונך להוסיף לרשימת המאומתים:", function(username) {
|
|
97
88
|
if (!username) return;
|
|
@@ -103,7 +94,6 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
103
94
|
title: "הזן מספר טלפון עבור " + username + " (אופציונלי)",
|
|
104
95
|
inputType: 'text',
|
|
105
96
|
callback: function(phone) {
|
|
106
|
-
// תיקון קריטי: אם לחצו ביטול, עצור
|
|
107
97
|
if (phone === null) return;
|
|
108
98
|
|
|
109
99
|
var confirmMsg = "<h4>סיכום פעולה</h4>" +
|
|
@@ -130,9 +120,16 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
130
120
|
var phone = $('#test-phone').val();
|
|
131
121
|
if(!phone) return alerts.error('נא להזין מספר לבדיקה');
|
|
132
122
|
|
|
133
|
-
$.post('/api/admin/plugins/phone-verification/test-call', { phoneNumber: phone, _csrf: config.csrf_token }, function(res) {
|
|
134
|
-
if(res.success)
|
|
135
|
-
|
|
123
|
+
$.post(config.relative_path + '/api/admin/plugins/phone-verification/test-call', { phoneNumber: phone, _csrf: config.csrf_token }, function(res) {
|
|
124
|
+
if(res.success) {
|
|
125
|
+
// עדכון: הצגת הקוד שהתקבל מהשרת למנהל
|
|
126
|
+
var msg = res.message || 'הצינתוק יצא בהצלחה';
|
|
127
|
+
if (res.code) {
|
|
128
|
+
msg += '<br><strong>קוד צפוי (4 ספרות אחרונות): ' + res.code + '</strong>';
|
|
129
|
+
}
|
|
130
|
+
alerts.success(msg);
|
|
131
|
+
}
|
|
132
|
+
else alerts.error(res.message || 'שגיאה בביצוע הבדיקה');
|
|
136
133
|
});
|
|
137
134
|
});
|
|
138
135
|
|
|
@@ -175,7 +172,7 @@ define('admin/plugins/phone-verification', ['settings', 'bootbox', 'alerts'], fu
|
|
|
175
172
|
$('#search-btn').on('click', function() {
|
|
176
173
|
var phone = $('#phone-search').val();
|
|
177
174
|
if (!phone) { loadUsers(1); return; }
|
|
178
|
-
$.get('/api/admin/plugins/phone-verification/search', { phone: phone }, function(data) {
|
|
175
|
+
$.get(config.relative_path + '/api/admin/plugins/phone-verification/search', { phone: phone }, function(data) {
|
|
179
176
|
usersTbody.empty();
|
|
180
177
|
if (data.success && data.found) usersTbody.append(buildUserRow(data.user));
|
|
181
178
|
else usersTbody.html('<tr><td colspan="6" class="text-center">לא נמצא משתמש</td></tr>');
|