jskelet 0.6.2 → 0.6.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.
- package/AGENTS.md +136 -136
- package/CHANGELOG.md +620 -596
- package/LICENSE +21 -21
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -309
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +661 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1443 -1423
- package/docs/07-yapilandirma.md +12 -6
- package/docs/08-build.md +429 -428
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +338 -338
- package/docs/12-panel-ve-oturum.md +478 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -328
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +669 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1453 -1431
- package/docs/en/07-configuration.md +1219 -1214
- package/docs/en/08-build.md +447 -446
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +340 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +488 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +17 -1
- package/src/config/index.js +13 -0
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +230 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -462
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -0
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1122
- package/src/server/image-optimizer.js +500 -407
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -66
- package/src/server/logs/pipeline.js +165 -158
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -100
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +356 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1058
- package/src/server/redis.js +588 -569
- package/src/server/render.js +4 -4
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +15 -1
- package/types/config/index.d.ts +8 -0
- package/types/server/cache-blob.d.ts +13 -0
- package/types/server/data-cache.d.ts +9 -0
- package/types/server/disk-cache.d.ts +36 -0
- package/types/server/html-cache.d.ts +26 -3
- package/types/server/logs/file-sink.d.ts +16 -5
- package/types/server/redis.d.ts +2 -1
package/src/server/admin/gate.js
CHANGED
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin paneli erişim kapısı: IP allowlist, bot UA engeli, ban sayacı.
|
|
3
|
-
*
|
|
4
|
-
* Yasaklı / reddedilen her cevap 404: 401/403 panelin varlığını doğrular.
|
|
5
|
-
*/
|
|
6
|
-
import net from "node:net";
|
|
7
|
-
|
|
8
|
-
/** Bilinen crawler / bot imzaları (küçük harf). */
|
|
9
|
-
const BOT_MARKERS = [
|
|
10
|
-
"googlebot",
|
|
11
|
-
"bingbot",
|
|
12
|
-
"slurp",
|
|
13
|
-
"duckduckbot",
|
|
14
|
-
"baiduspider",
|
|
15
|
-
"yandexbot",
|
|
16
|
-
"sogou",
|
|
17
|
-
"exabot",
|
|
18
|
-
"facebot",
|
|
19
|
-
"facebookexternalhit",
|
|
20
|
-
"ia_archiver",
|
|
21
|
-
"applebot",
|
|
22
|
-
"semrushbot",
|
|
23
|
-
"ahrefsbot",
|
|
24
|
-
"mj12bot",
|
|
25
|
-
"dotbot",
|
|
26
|
-
"petalbot",
|
|
27
|
-
"bytespider",
|
|
28
|
-
"gptbot",
|
|
29
|
-
"claudebot",
|
|
30
|
-
"ccbot",
|
|
31
|
-
"amazonbot",
|
|
32
|
-
"twitterbot",
|
|
33
|
-
"linkedinbot",
|
|
34
|
-
"embedly",
|
|
35
|
-
"quora link preview",
|
|
36
|
-
"showyoubot",
|
|
37
|
-
"outbrain",
|
|
38
|
-
"pinterest",
|
|
39
|
-
"redditbot",
|
|
40
|
-
"whatsapp",
|
|
41
|
-
"telegrambot",
|
|
42
|
-
"discordbot",
|
|
43
|
-
"crawler",
|
|
44
|
-
"spider",
|
|
45
|
-
"bot/",
|
|
46
|
-
"bot;",
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/** @type {Map<string, { fails: number, bannedUntil: number }>} */
|
|
50
|
-
const offenders = new Map();
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {import('express').Request} req
|
|
54
|
-
* @returns {string}
|
|
55
|
-
*/
|
|
56
|
-
export function clientKey(req) {
|
|
57
|
-
// `trust proxy` ayarı `req.ip`e yansıyor; ters proxy arkasında doğru IP,
|
|
58
|
-
// doğrudan internete açık bir sunucuda soket adresi kullanılır.
|
|
59
|
-
return req.ip ?? req.socket.remoteAddress ?? "unknown";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {string} ua
|
|
64
|
-
* @returns {boolean}
|
|
65
|
-
*/
|
|
66
|
-
export function looksLikeBot(ua) {
|
|
67
|
-
if (!ua) return true;
|
|
68
|
-
const lower = ua.toLowerCase();
|
|
69
|
-
return BOT_MARKERS.some((marker) => lower.includes(marker));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Exact IP veya CIDR (`10.0.0.0/8`). IPv6 için yalnızca exact eşleşme.
|
|
74
|
-
*
|
|
75
|
-
* @param {string} ip
|
|
76
|
-
* @param {string[]} allowIps
|
|
77
|
-
* @returns {boolean}
|
|
78
|
-
*/
|
|
79
|
-
export function ipAllowed(ip, allowIps) {
|
|
80
|
-
if (!allowIps.length) return true;
|
|
81
|
-
|
|
82
|
-
const normalized = normalizeIp(ip);
|
|
83
|
-
if (!normalized) return false;
|
|
84
|
-
|
|
85
|
-
for (const entry of allowIps) {
|
|
86
|
-
if (entry.includes("/")) {
|
|
87
|
-
if (matchCidr(normalized, entry)) return true;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (normalizeIp(entry) === normalized) return true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {string} ip
|
|
98
|
-
* @returns {string | null}
|
|
99
|
-
*/
|
|
100
|
-
function normalizeIp(ip) {
|
|
101
|
-
if (!ip) return null;
|
|
102
|
-
// Express IPv4-mapped IPv6: `:ffff:127.0.0.1`
|
|
103
|
-
const mapped = ip.startsWith("::ffff:") ? ip.slice(7) : ip;
|
|
104
|
-
return mapped;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {string} ip
|
|
109
|
-
* @param {string} cidr
|
|
110
|
-
* @returns {boolean}
|
|
111
|
-
*/
|
|
112
|
-
function matchCidr(ip, cidr) {
|
|
113
|
-
const [network, bitsRaw] = cidr.split("/");
|
|
114
|
-
const bits = Number(bitsRaw);
|
|
115
|
-
if (!Number.isFinite(bits) || bits < 0 || bits > 32) return false;
|
|
116
|
-
if (net.isIPv4(ip) && net.isIPv4(network)) {
|
|
117
|
-
const ipNum = ipv4ToInt(ip);
|
|
118
|
-
const netNum = ipv4ToInt(network);
|
|
119
|
-
if (ipNum === null || netNum === null) return false;
|
|
120
|
-
const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;
|
|
121
|
-
return (ipNum & mask) === (netNum & mask);
|
|
122
|
-
}
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {string} ip
|
|
128
|
-
* @returns {number | null}
|
|
129
|
-
*/
|
|
130
|
-
function ipv4ToInt(ip) {
|
|
131
|
-
const parts = ip.split(".").map(Number);
|
|
132
|
-
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* @param {import('express').Request} req
|
|
140
|
-
* @returns {boolean}
|
|
141
|
-
*/
|
|
142
|
-
export function banned(req) {
|
|
143
|
-
const record = offenders.get(clientKey(req));
|
|
144
|
-
if (!record) return false;
|
|
145
|
-
|
|
146
|
-
if (record.bannedUntil && Date.now() >= record.bannedUntil) {
|
|
147
|
-
offenders.delete(clientKey(req));
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return record.bannedUntil > 0;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @param {import('express').Request} req
|
|
156
|
-
* @param {string} reason
|
|
157
|
-
* @param {{ banAttempts: number, banHours: number }} settings
|
|
158
|
-
*/
|
|
159
|
-
export function noteFailure(req, reason, settings) {
|
|
160
|
-
const key = clientKey(req);
|
|
161
|
-
const record = offenders.get(key) ?? { fails: 0, bannedUntil: 0 };
|
|
162
|
-
record.fails += 1;
|
|
163
|
-
|
|
164
|
-
if (record.fails >= settings.banAttempts) {
|
|
165
|
-
record.bannedUntil = Date.now() + settings.banHours * 3600_000;
|
|
166
|
-
console.warn(
|
|
167
|
-
`[admin] ${key} banned for ${settings.banHours}h after ` +
|
|
168
|
-
`${record.fails} failed attempts (${reason})`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
offenders.set(key, record);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* @param {import('express').Request} req
|
|
177
|
-
*/
|
|
178
|
-
export function clearOffender(req) {
|
|
179
|
-
offenders.delete(clientKey(req));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Kapı middleware'i: ban → IP → bot. Hepsi 404.
|
|
184
|
-
*
|
|
185
|
-
* @param {() => typeof import('../../config/defaults.js').DEFAULT_ADMIN} getSettings
|
|
186
|
-
* @returns {import('express').RequestHandler}
|
|
187
|
-
*/
|
|
188
|
-
export function gateMiddleware(getSettings) {
|
|
189
|
-
return (req, res, next) => {
|
|
190
|
-
if (banned(req)) {
|
|
191
|
-
res.status(404).type("text/plain").send("Not Found");
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const settings = getSettings();
|
|
196
|
-
|
|
197
|
-
if (!ipAllowed(clientKey(req), settings.allowIps)) {
|
|
198
|
-
res.status(404).type("text/plain").send("Not Found");
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (settings.blockBots && looksLikeBot(String(req.headers["user-agent"] ?? ""))) {
|
|
203
|
-
res.status(404).type("text/plain").send("Not Found");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
next();
|
|
208
|
-
};
|
|
209
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Admin paneli erişim kapısı: IP allowlist, bot UA engeli, ban sayacı.
|
|
3
|
+
*
|
|
4
|
+
* Yasaklı / reddedilen her cevap 404: 401/403 panelin varlığını doğrular.
|
|
5
|
+
*/
|
|
6
|
+
import net from "node:net";
|
|
7
|
+
|
|
8
|
+
/** Bilinen crawler / bot imzaları (küçük harf). */
|
|
9
|
+
const BOT_MARKERS = [
|
|
10
|
+
"googlebot",
|
|
11
|
+
"bingbot",
|
|
12
|
+
"slurp",
|
|
13
|
+
"duckduckbot",
|
|
14
|
+
"baiduspider",
|
|
15
|
+
"yandexbot",
|
|
16
|
+
"sogou",
|
|
17
|
+
"exabot",
|
|
18
|
+
"facebot",
|
|
19
|
+
"facebookexternalhit",
|
|
20
|
+
"ia_archiver",
|
|
21
|
+
"applebot",
|
|
22
|
+
"semrushbot",
|
|
23
|
+
"ahrefsbot",
|
|
24
|
+
"mj12bot",
|
|
25
|
+
"dotbot",
|
|
26
|
+
"petalbot",
|
|
27
|
+
"bytespider",
|
|
28
|
+
"gptbot",
|
|
29
|
+
"claudebot",
|
|
30
|
+
"ccbot",
|
|
31
|
+
"amazonbot",
|
|
32
|
+
"twitterbot",
|
|
33
|
+
"linkedinbot",
|
|
34
|
+
"embedly",
|
|
35
|
+
"quora link preview",
|
|
36
|
+
"showyoubot",
|
|
37
|
+
"outbrain",
|
|
38
|
+
"pinterest",
|
|
39
|
+
"redditbot",
|
|
40
|
+
"whatsapp",
|
|
41
|
+
"telegrambot",
|
|
42
|
+
"discordbot",
|
|
43
|
+
"crawler",
|
|
44
|
+
"spider",
|
|
45
|
+
"bot/",
|
|
46
|
+
"bot;",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** @type {Map<string, { fails: number, bannedUntil: number }>} */
|
|
50
|
+
const offenders = new Map();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {import('express').Request} req
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function clientKey(req) {
|
|
57
|
+
// `trust proxy` ayarı `req.ip`e yansıyor; ters proxy arkasında doğru IP,
|
|
58
|
+
// doğrudan internete açık bir sunucuda soket adresi kullanılır.
|
|
59
|
+
return req.ip ?? req.socket.remoteAddress ?? "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} ua
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export function looksLikeBot(ua) {
|
|
67
|
+
if (!ua) return true;
|
|
68
|
+
const lower = ua.toLowerCase();
|
|
69
|
+
return BOT_MARKERS.some((marker) => lower.includes(marker));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Exact IP veya CIDR (`10.0.0.0/8`). IPv6 için yalnızca exact eşleşme.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} ip
|
|
76
|
+
* @param {string[]} allowIps
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
export function ipAllowed(ip, allowIps) {
|
|
80
|
+
if (!allowIps.length) return true;
|
|
81
|
+
|
|
82
|
+
const normalized = normalizeIp(ip);
|
|
83
|
+
if (!normalized) return false;
|
|
84
|
+
|
|
85
|
+
for (const entry of allowIps) {
|
|
86
|
+
if (entry.includes("/")) {
|
|
87
|
+
if (matchCidr(normalized, entry)) return true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (normalizeIp(entry) === normalized) return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} ip
|
|
98
|
+
* @returns {string | null}
|
|
99
|
+
*/
|
|
100
|
+
function normalizeIp(ip) {
|
|
101
|
+
if (!ip) return null;
|
|
102
|
+
// Express IPv4-mapped IPv6: `:ffff:127.0.0.1`
|
|
103
|
+
const mapped = ip.startsWith("::ffff:") ? ip.slice(7) : ip;
|
|
104
|
+
return mapped;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {string} ip
|
|
109
|
+
* @param {string} cidr
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
function matchCidr(ip, cidr) {
|
|
113
|
+
const [network, bitsRaw] = cidr.split("/");
|
|
114
|
+
const bits = Number(bitsRaw);
|
|
115
|
+
if (!Number.isFinite(bits) || bits < 0 || bits > 32) return false;
|
|
116
|
+
if (net.isIPv4(ip) && net.isIPv4(network)) {
|
|
117
|
+
const ipNum = ipv4ToInt(ip);
|
|
118
|
+
const netNum = ipv4ToInt(network);
|
|
119
|
+
if (ipNum === null || netNum === null) return false;
|
|
120
|
+
const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;
|
|
121
|
+
return (ipNum & mask) === (netNum & mask);
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} ip
|
|
128
|
+
* @returns {number | null}
|
|
129
|
+
*/
|
|
130
|
+
function ipv4ToInt(ip) {
|
|
131
|
+
const parts = ip.split(".").map(Number);
|
|
132
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {import('express').Request} req
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
export function banned(req) {
|
|
143
|
+
const record = offenders.get(clientKey(req));
|
|
144
|
+
if (!record) return false;
|
|
145
|
+
|
|
146
|
+
if (record.bannedUntil && Date.now() >= record.bannedUntil) {
|
|
147
|
+
offenders.delete(clientKey(req));
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return record.bannedUntil > 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {import('express').Request} req
|
|
156
|
+
* @param {string} reason
|
|
157
|
+
* @param {{ banAttempts: number, banHours: number }} settings
|
|
158
|
+
*/
|
|
159
|
+
export function noteFailure(req, reason, settings) {
|
|
160
|
+
const key = clientKey(req);
|
|
161
|
+
const record = offenders.get(key) ?? { fails: 0, bannedUntil: 0 };
|
|
162
|
+
record.fails += 1;
|
|
163
|
+
|
|
164
|
+
if (record.fails >= settings.banAttempts) {
|
|
165
|
+
record.bannedUntil = Date.now() + settings.banHours * 3600_000;
|
|
166
|
+
console.warn(
|
|
167
|
+
`[admin] ${key} banned for ${settings.banHours}h after ` +
|
|
168
|
+
`${record.fails} failed attempts (${reason})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
offenders.set(key, record);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {import('express').Request} req
|
|
177
|
+
*/
|
|
178
|
+
export function clearOffender(req) {
|
|
179
|
+
offenders.delete(clientKey(req));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Kapı middleware'i: ban → IP → bot. Hepsi 404.
|
|
184
|
+
*
|
|
185
|
+
* @param {() => typeof import('../../config/defaults.js').DEFAULT_ADMIN} getSettings
|
|
186
|
+
* @returns {import('express').RequestHandler}
|
|
187
|
+
*/
|
|
188
|
+
export function gateMiddleware(getSettings) {
|
|
189
|
+
return (req, res, next) => {
|
|
190
|
+
if (banned(req)) {
|
|
191
|
+
res.status(404).type("text/plain").send("Not Found");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const settings = getSettings();
|
|
196
|
+
|
|
197
|
+
if (!ipAllowed(clientKey(req), settings.allowIps)) {
|
|
198
|
+
res.status(404).type("text/plain").send("Not Found");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (settings.blockBots && looksLikeBot(String(req.headers["user-agent"] ?? ""))) {
|
|
203
|
+
res.status(404).type("text/plain").send("Not Found");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
next();
|
|
208
|
+
};
|
|
209
|
+
}
|