@arnaudw38/nodebb-plugin-spam-be-gone 1.0.5 → 1.0.7
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/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/public/js/scripts.js +89 -26
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2026-02-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Fixed Turnstile login retry reset in themes/login flows where previous click/enter hooks did not reliably trigger the widget reset.
|
|
9
|
+
- Made login retry reset logic target the current Turnstile container dynamically instead of relying on a stale target id.
|
|
10
|
+
- Added a fallback reset trigger when login error alerts are rendered on the login page.
|
|
11
|
+
|
|
12
|
+
## [1.0.7] - 2026-02-25
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Login retry Turnstile reset now uses native event listeners only (removed jQuery delegated login listeners).
|
|
16
|
+
- Prevented duplicate retry-reset triggers caused by mixed jQuery + native listeners on some NodeBB themes.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Simplified login retry detection to click + Enter key flows (removed submit listener).
|
|
20
|
+
|
|
21
|
+
## [1.0.6] - 2026-02-25
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Fixed login retry Turnstile reset trigger on NodeBB login flows that do not emit the expected jQuery submit/click events.
|
|
25
|
+
- Prevented missing reset after failed login without page reload by adding capture-phase submit/click/Enter listeners.
|
|
26
|
+
|
|
5
27
|
## [1.0.5] - 2026-02-25
|
|
6
28
|
|
|
7
29
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnaudw38/nodebb-plugin-spam-be-gone",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Anti-spam plugin for NodeBB 4.x using Akismet, StopForumSpam API, ProjectHoneyPot, and Cloudflare Turnstile (Turnstile-only fork)",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"scripts": {},
|
package/public/js/scripts.js
CHANGED
|
@@ -8,6 +8,7 @@ $(function () {
|
|
|
8
8
|
var turnstileState = {
|
|
9
9
|
widgets: {},
|
|
10
10
|
loginBindingsAttached: false,
|
|
11
|
+
loginResetTimers: [],
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
function getTurnstileArgs() {
|
|
@@ -34,59 +35,121 @@ $(function () {
|
|
|
34
35
|
return input && input.value;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function getCurrentLoginTurnstileTargetId() {
|
|
39
|
+
var args = getTurnstileArgs();
|
|
40
|
+
return args && args.targetId;
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
function resetTurnstileWidget(targetId) {
|
|
38
44
|
if (typeof turnstile === 'undefined') {
|
|
39
|
-
return;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
targetId = targetId || getCurrentLoginTurnstileTargetId();
|
|
48
|
+
if (!targetId) {
|
|
49
|
+
return false;
|
|
40
50
|
}
|
|
41
51
|
var widgetId = turnstileState.widgets[targetId];
|
|
42
52
|
if (widgetId === undefined || widgetId === null) {
|
|
43
|
-
return;
|
|
53
|
+
return false;
|
|
44
54
|
}
|
|
45
55
|
try {
|
|
46
56
|
turnstile.reset(widgetId);
|
|
57
|
+
return true;
|
|
47
58
|
} catch (err) {
|
|
48
59
|
// Ignore reset errors when the widget is already destroyed by navigation.
|
|
60
|
+
return false;
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
function scheduleLoginTurnstileReset(
|
|
53
|
-
// Turnstile tokens are single-use. On failed login, NodeBB keeps the user on the
|
|
54
|
-
// same page
|
|
55
|
-
|
|
56
|
-
window.
|
|
57
|
-
|
|
64
|
+
function scheduleLoginTurnstileReset() {
|
|
65
|
+
// Turnstile tokens are single-use. On failed login, NodeBB often keeps the user on the
|
|
66
|
+
// same page and may handle submission via AJAX/click handlers. We try multiple delayed resets.
|
|
67
|
+
turnstileState.loginResetTimers.forEach(function (timerId) {
|
|
68
|
+
window.clearTimeout(timerId);
|
|
69
|
+
});
|
|
70
|
+
turnstileState.loginResetTimers = [];
|
|
71
|
+
|
|
72
|
+
[700, 1400, 2600, 4200].forEach(function (delay) {
|
|
73
|
+
var timerId = window.setTimeout(function () {
|
|
74
|
+
var onLoginPage = !ajaxify.data || !ajaxify.data.tpl_url || ajaxify.data.tpl_url === 'login';
|
|
75
|
+
if (!onLoginPage) {
|
|
58
76
|
return;
|
|
59
77
|
}
|
|
60
|
-
|
|
78
|
+
var targetId = getCurrentLoginTurnstileTargetId();
|
|
79
|
+
if (!targetId || !document.getElementById(targetId)) {
|
|
61
80
|
return;
|
|
62
81
|
}
|
|
63
|
-
// Reset unconditionally if we're still on the login page after submit.
|
|
64
|
-
// Tokens are single-use and may already have been cleared by the form logic
|
|
65
|
-
// when a login attempt fails, so checking the hidden input is unreliable.
|
|
66
82
|
resetTurnstileWidget(targetId);
|
|
67
83
|
}, delay);
|
|
84
|
+
turnstileState.loginResetTimers.push(timerId);
|
|
68
85
|
});
|
|
69
86
|
}
|
|
70
87
|
|
|
71
|
-
function bindLoginRetryReset(
|
|
88
|
+
function bindLoginRetryReset() {
|
|
72
89
|
if (turnstileState.loginBindingsAttached) {
|
|
73
90
|
return;
|
|
74
91
|
}
|
|
75
92
|
turnstileState.loginBindingsAttached = true;
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
function loginSubmitTrigger() {
|
|
95
|
+
scheduleLoginTurnstileReset();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Native capture listeners only (avoid duplicate handlers/race conditions with jQuery delegates).
|
|
99
|
+
document.addEventListener('click', function (ev) {
|
|
100
|
+
var btn = ev.target && ev.target.closest ? ev.target.closest('[component="login/submit"], #login [type="submit"], form[action*="/login"] [type="submit"], form[data-action="login"] [type="submit"], button[type="submit"]') : null;
|
|
101
|
+
if (!btn) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
var inLogin = btn.closest && btn.closest('#login, form[action*="/login"], form[data-action="login"], [component="login"]');
|
|
105
|
+
if (inLogin || (ajaxify.data && ajaxify.data.tpl_url === 'login')) {
|
|
106
|
+
loginSubmitTrigger();
|
|
107
|
+
}
|
|
108
|
+
}, true);
|
|
109
|
+
|
|
110
|
+
// Enter key in login fields for themes that trigger login without a click event.
|
|
111
|
+
document.addEventListener('keydown', function (ev) {
|
|
112
|
+
if (ev.key !== 'Enter') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
var el = ev.target;
|
|
116
|
+
if (!el || !el.closest) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
var inLogin = el.closest('#login, form[action*="/login"], form[data-action="login"], [component="login"]');
|
|
120
|
+
if (inLogin || (ajaxify.data && ajaxify.data.tpl_url === 'login')) {
|
|
121
|
+
loginSubmitTrigger();
|
|
122
|
+
}
|
|
123
|
+
}, true);
|
|
124
|
+
|
|
125
|
+
// Extra safety: reset again when a login error alert appears (covers AJAX flows that do not
|
|
126
|
+
// reliably trigger the expected submit/click path in some themes).
|
|
127
|
+
if (window.MutationObserver) {
|
|
128
|
+
var observer = new MutationObserver(function (mutations) {
|
|
129
|
+
var onLoginPage = !ajaxify.data || !ajaxify.data.tpl_url || ajaxify.data.tpl_url === 'login';
|
|
130
|
+
if (!onLoginPage) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (var i = 0; i < mutations.length; i += 1) {
|
|
134
|
+
for (var j = 0; j < mutations[i].addedNodes.length; j += 1) {
|
|
135
|
+
var n = mutations[i].addedNodes[j];
|
|
136
|
+
if (!n || n.nodeType !== 1) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
var hasError = (n.matches && n.matches('.alert-danger, .alert-error, .text-danger, [component="alerts/error"]')) ||
|
|
140
|
+
(n.querySelector && n.querySelector('.alert-danger, .alert-error, .text-danger, [component="alerts/error"]'));
|
|
141
|
+
if (hasError) {
|
|
142
|
+
scheduleLoginTurnstileReset();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
149
|
+
}
|
|
87
150
|
}
|
|
88
151
|
|
|
89
|
-
|
|
152
|
+
function renderTurnstileIfNeeded(isLoginPage) {
|
|
90
153
|
var args = getTurnstileArgs();
|
|
91
154
|
if (!args || (isLoginPage && !args.addLoginTurnstile)) {
|
|
92
155
|
return;
|
|
@@ -104,7 +167,7 @@ $(function () {
|
|
|
104
167
|
|
|
105
168
|
if (target.dataset.turnstileRendered === '1') {
|
|
106
169
|
if (isLoginPage) {
|
|
107
|
-
bindLoginRetryReset(
|
|
170
|
+
bindLoginRetryReset();
|
|
108
171
|
}
|
|
109
172
|
return;
|
|
110
173
|
}
|
|
@@ -134,7 +197,7 @@ $(function () {
|
|
|
134
197
|
target.dataset.turnstileRendered = '1';
|
|
135
198
|
turnstileState.widgets[args.targetId] = widgetId;
|
|
136
199
|
if (isLoginPage) {
|
|
137
|
-
bindLoginRetryReset(
|
|
200
|
+
bindLoginRetryReset();
|
|
138
201
|
}
|
|
139
202
|
})
|
|
140
203
|
.catch(function () {
|