specnav-middleware 0.2.0 → 0.2.2
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 +6 -0
- package/dist/index.js +13 -6
- package/dist/index.js.map +1 -1
- package/dist/{index.cjs → index.mjs} +11 -12
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var server = require('next/server');
|
|
2
4
|
|
|
3
5
|
// src/index.ts
|
|
4
6
|
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
@@ -20,25 +22,28 @@ function specnavMiddleware(request) {
|
|
|
20
22
|
if (!isSpecnav) return null;
|
|
21
23
|
const ip = request.headers.get("x-forwarded-for")?.split(",")[0] || request.headers.get("x-real-ip") || "unknown";
|
|
22
24
|
if (!checkRateLimit(ip)) {
|
|
23
|
-
return new NextResponse("Too Many Requests", { status: 429 });
|
|
25
|
+
return new server.NextResponse("Too Many Requests", { status: 429 });
|
|
24
26
|
}
|
|
25
27
|
const origin = request.headers.get("origin");
|
|
26
28
|
const referer = request.headers.get("referer");
|
|
27
29
|
if (!origin && !referer) {
|
|
28
|
-
return new NextResponse("Forbidden", { status: 403 });
|
|
30
|
+
return new server.NextResponse("Forbidden", { status: 403 });
|
|
29
31
|
}
|
|
30
32
|
const requestOrigin = origin || new URL(referer).origin;
|
|
31
33
|
const serverOrigin = new URL(request.url).origin;
|
|
32
34
|
if (requestOrigin !== serverOrigin) {
|
|
33
|
-
return new NextResponse("Forbidden", { status: 403 });
|
|
35
|
+
return new server.NextResponse("Forbidden", { status: 403 });
|
|
34
36
|
}
|
|
35
37
|
const headers = new Headers(request.headers);
|
|
36
38
|
headers.set("x-specnav-partial", "1");
|
|
37
|
-
|
|
39
|
+
const response = server.NextResponse.next({
|
|
38
40
|
request: {
|
|
39
41
|
headers
|
|
40
42
|
}
|
|
41
43
|
});
|
|
44
|
+
response.headers.set("Cache-Control", "max-age=60, stale-while-revalidate=300");
|
|
45
|
+
response.headers.set("X-Specnav-Partial", "1");
|
|
46
|
+
return response;
|
|
42
47
|
}
|
|
43
48
|
function isSpecnavRequest(request) {
|
|
44
49
|
return request.headers.get("x-specnav") === "1";
|
|
@@ -47,6 +52,8 @@ function isPartialRequest(request) {
|
|
|
47
52
|
return request.headers.get("x-specnav-partial") === "1";
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
exports.isPartialRequest = isPartialRequest;
|
|
56
|
+
exports.isSpecnavRequest = isSpecnavRequest;
|
|
57
|
+
exports.specnavMiddleware = specnavMiddleware;
|
|
51
58
|
//# sourceMappingURL=index.js.map
|
|
52
59
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["NextResponse"],"mappings":"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","file":"index.js","sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\n// Simple in-memory rate limiter\n// ⚠️ WARNING: This rate limiter is NOT effective on serverless platforms (Vercel Edge, AWS Lambda, etc.)\n// where each cold start creates a fresh module scope, resetting the Map. For production serverless use,\n// replace with a persistent store like Redis, Upstash, Vercel KV, or a distributed rate limiting service.\nconst rateLimitMap = new Map<string, { count: number; resetAt: number }>();\n\nfunction checkRateLimit(ip: string, limit = 100, windowMs = 60000): boolean {\n const now = Date.now();\n const record = rateLimitMap.get(ip);\n\n if (!record || now > record.resetAt) {\n rateLimitMap.set(ip, { count: 1, resetAt: now + windowMs });\n return true;\n }\n\n if (record.count >= limit) {\n return false;\n }\n\n record.count++;\n return true;\n}\n\nexport function specnavMiddleware(request: NextRequest): NextResponse | null {\n const isSpecnav = request.headers.get(\"x-specnav\") === \"1\";\n\n if (!isSpecnav) return null;\n\n // Rate limiting\n const ip = request.headers.get(\"x-forwarded-for\")?.split(\",\")[0] || \n request.headers.get(\"x-real-ip\") || \n \"unknown\";\n if (!checkRateLimit(ip)) {\n return new NextResponse(\"Too Many Requests\", { status: 429 });\n }\n\n // Security: Verify same-origin (deny if headers missing)\n const origin = request.headers.get(\"origin\");\n const referer = request.headers.get(\"referer\");\n \n if (!origin && !referer) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n \n const requestOrigin = origin || new URL(referer!).origin;\n const serverOrigin = new URL(request.url).origin;\n \n if (requestOrigin !== serverOrigin) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n\n // Clone request and add internal header for route handlers\n const headers = new Headers(request.headers);\n headers.set(\"x-specnav-partial\", \"1\");\n\n const response = NextResponse.next({\n request: {\n headers,\n },\n });\n\n // Set cache headers for specnav requests\n response.headers.set(\"Cache-Control\", \"max-age=60, stale-while-revalidate=300\");\n response.headers.set(\"X-Specnav-Partial\", \"1\");\n\n return response;\n}\n\nexport function isSpecnavRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav\") === \"1\";\n}\n\nexport function isPartialRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav-partial\") === \"1\";\n}\n"]}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var server = require('next/server');
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
4
2
|
|
|
5
3
|
// src/index.ts
|
|
6
4
|
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
@@ -22,25 +20,28 @@ function specnavMiddleware(request) {
|
|
|
22
20
|
if (!isSpecnav) return null;
|
|
23
21
|
const ip = request.headers.get("x-forwarded-for")?.split(",")[0] || request.headers.get("x-real-ip") || "unknown";
|
|
24
22
|
if (!checkRateLimit(ip)) {
|
|
25
|
-
return new
|
|
23
|
+
return new NextResponse("Too Many Requests", { status: 429 });
|
|
26
24
|
}
|
|
27
25
|
const origin = request.headers.get("origin");
|
|
28
26
|
const referer = request.headers.get("referer");
|
|
29
27
|
if (!origin && !referer) {
|
|
30
|
-
return new
|
|
28
|
+
return new NextResponse("Forbidden", { status: 403 });
|
|
31
29
|
}
|
|
32
30
|
const requestOrigin = origin || new URL(referer).origin;
|
|
33
31
|
const serverOrigin = new URL(request.url).origin;
|
|
34
32
|
if (requestOrigin !== serverOrigin) {
|
|
35
|
-
return new
|
|
33
|
+
return new NextResponse("Forbidden", { status: 403 });
|
|
36
34
|
}
|
|
37
35
|
const headers = new Headers(request.headers);
|
|
38
36
|
headers.set("x-specnav-partial", "1");
|
|
39
|
-
|
|
37
|
+
const response = NextResponse.next({
|
|
40
38
|
request: {
|
|
41
39
|
headers
|
|
42
40
|
}
|
|
43
41
|
});
|
|
42
|
+
response.headers.set("Cache-Control", "max-age=60, stale-while-revalidate=300");
|
|
43
|
+
response.headers.set("X-Specnav-Partial", "1");
|
|
44
|
+
return response;
|
|
44
45
|
}
|
|
45
46
|
function isSpecnavRequest(request) {
|
|
46
47
|
return request.headers.get("x-specnav") === "1";
|
|
@@ -49,8 +50,6 @@ function isPartialRequest(request) {
|
|
|
49
50
|
return request.headers.get("x-specnav-partial") === "1";
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//# sourceMappingURL=index.cjs.map
|
|
56
|
-
//# sourceMappingURL=index.cjs.map
|
|
53
|
+
export { isPartialRequest, isSpecnavRequest, specnavMiddleware };
|
|
54
|
+
//# sourceMappingURL=index.mjs.map
|
|
55
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAOA,IAAM,YAAA,uBAAmB,GAAA,EAAgD;AAEzE,SAAS,cAAA,CAAe,EAAA,EAAY,KAAA,GAAQ,GAAA,EAAK,WAAW,GAAA,EAAgB;AAC1E,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,CAAI,EAAE,CAAA;AAElC,EAAA,IAAI,CAAC,MAAA,IAAU,GAAA,GAAM,MAAA,CAAO,OAAA,EAAS;AACnC,IAAA,YAAA,CAAa,GAAA,CAAI,IAAI,EAAE,KAAA,EAAO,GAAG,OAAA,EAAS,GAAA,GAAM,UAAU,CAAA;AAC1D,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAA,CAAO,KAAA,EAAA;AACP,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,kBAAkB,OAAA,EAA2C;AAC3E,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,KAAM,GAAA;AAEvD,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AAGvB,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,EAAG,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,IACpD,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,IAC/B,SAAA;AACX,EAAA,IAAI,CAAC,cAAA,CAAe,EAAE,CAAA,EAAG;AACvB,IAAA,OAAO,IAAI,YAAA,CAAa,mBAAA,EAAqB,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EAC9D;AAGA,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA;AAE7C,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAA,EAAS;AACvB,IAAA,OAAO,IAAI,YAAA,CAAa,WAAA,EAAa,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,aAAA,GAAgB,MAAA,IAAU,IAAI,GAAA,CAAI,OAAQ,CAAA,CAAE,MAAA;AAClD,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,CAAE,MAAA;AAE1C,EAAA,IAAI,kBAAkB,YAAA,EAAc;AAClC,IAAA,OAAO,IAAI,YAAA,CAAa,WAAA,EAAa,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EACtD;AAGA,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AAC3C,EAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,GAAG,CAAA;AAEpC,EAAA,MAAM,QAAA,GAAW,aAAa,IAAA,CAAK;AAAA,IACjC,OAAA,EAAS;AAAA,MACP;AAAA;AACF,GACD,CAAA;AAGD,EAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,eAAA,EAAiB,wCAAwC,CAAA;AAC9E,EAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,mBAAA,EAAqB,GAAG,CAAA;AAE7C,EAAA,OAAO,QAAA;AACT;AAEO,SAAS,iBAAiB,OAAA,EAA+B;AAC9D,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,KAAM,GAAA;AAC9C;AAEO,SAAS,iBAAiB,OAAA,EAA+B;AAC9D,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,KAAM,GAAA;AACtD","file":"index.mjs","sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\n// Simple in-memory rate limiter\n// ⚠️ WARNING: This rate limiter is NOT effective on serverless platforms (Vercel Edge, AWS Lambda, etc.)\n// where each cold start creates a fresh module scope, resetting the Map. For production serverless use,\n// replace with a persistent store like Redis, Upstash, Vercel KV, or a distributed rate limiting service.\nconst rateLimitMap = new Map<string, { count: number; resetAt: number }>();\n\nfunction checkRateLimit(ip: string, limit = 100, windowMs = 60000): boolean {\n const now = Date.now();\n const record = rateLimitMap.get(ip);\n\n if (!record || now > record.resetAt) {\n rateLimitMap.set(ip, { count: 1, resetAt: now + windowMs });\n return true;\n }\n\n if (record.count >= limit) {\n return false;\n }\n\n record.count++;\n return true;\n}\n\nexport function specnavMiddleware(request: NextRequest): NextResponse | null {\n const isSpecnav = request.headers.get(\"x-specnav\") === \"1\";\n\n if (!isSpecnav) return null;\n\n // Rate limiting\n const ip = request.headers.get(\"x-forwarded-for\")?.split(\",\")[0] || \n request.headers.get(\"x-real-ip\") || \n \"unknown\";\n if (!checkRateLimit(ip)) {\n return new NextResponse(\"Too Many Requests\", { status: 429 });\n }\n\n // Security: Verify same-origin (deny if headers missing)\n const origin = request.headers.get(\"origin\");\n const referer = request.headers.get(\"referer\");\n \n if (!origin && !referer) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n \n const requestOrigin = origin || new URL(referer!).origin;\n const serverOrigin = new URL(request.url).origin;\n \n if (requestOrigin !== serverOrigin) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n\n // Clone request and add internal header for route handlers\n const headers = new Headers(request.headers);\n headers.set(\"x-specnav-partial\", \"1\");\n\n const response = NextResponse.next({\n request: {\n headers,\n },\n });\n\n // Set cache headers for specnav requests\n response.headers.set(\"Cache-Control\", \"max-age=60, stale-while-revalidate=300\");\n response.headers.set(\"X-Specnav-Partial\", \"1\");\n\n return response;\n}\n\nexport function isSpecnavRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav\") === \"1\";\n}\n\nexport function isPartialRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav-partial\") === \"1\";\n}\n"]}
|
package/package.json
CHANGED
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["NextResponse"],"mappings":";;;;;AAOA,IAAM,YAAA,uBAAmB,GAAA,EAAgD;AAEzE,SAAS,cAAA,CAAe,EAAA,EAAY,KAAA,GAAQ,GAAA,EAAK,WAAW,GAAA,EAAgB;AAC1E,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,CAAI,EAAE,CAAA;AAElC,EAAA,IAAI,CAAC,MAAA,IAAU,GAAA,GAAM,MAAA,CAAO,OAAA,EAAS;AACnC,IAAA,YAAA,CAAa,GAAA,CAAI,IAAI,EAAE,KAAA,EAAO,GAAG,OAAA,EAAS,GAAA,GAAM,UAAU,CAAA;AAC1D,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAA,CAAO,KAAA,EAAA;AACP,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,kBAAkB,OAAA,EAA2C;AAC3E,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,KAAM,GAAA;AAEvD,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AAGvB,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,EAAG,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,IACpD,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,IAC/B,SAAA;AACX,EAAA,IAAI,CAAC,cAAA,CAAe,EAAE,CAAA,EAAG;AACvB,IAAA,OAAO,IAAIA,mBAAA,CAAa,mBAAA,EAAqB,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EAC9D;AAGA,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA;AAE7C,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAA,EAAS;AACvB,IAAA,OAAO,IAAIA,mBAAA,CAAa,WAAA,EAAa,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,aAAA,GAAgB,MAAA,IAAU,IAAI,GAAA,CAAI,OAAQ,CAAA,CAAE,MAAA;AAClD,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,CAAE,MAAA;AAE1C,EAAA,IAAI,kBAAkB,YAAA,EAAc;AAClC,IAAA,OAAO,IAAIA,mBAAA,CAAa,WAAA,EAAa,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,EACtD;AAGA,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AAC3C,EAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,GAAG,CAAA;AAEpC,EAAA,OAAOA,oBAAa,IAAA,CAAK;AAAA,IACvB,OAAA,EAAS;AAAA,MACP;AAAA;AACF,GACD,CAAA;AACH;AAEO,SAAS,iBAAiB,OAAA,EAA+B;AAC9D,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,KAAM,GAAA;AAC9C;AAEO,SAAS,iBAAiB,OAAA,EAA+B;AAC9D,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,KAAM,GAAA;AACtD","file":"index.cjs","sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\n// Simple in-memory rate limiter\n// ⚠️ WARNING: This rate limiter is NOT effective on serverless platforms (Vercel Edge, AWS Lambda, etc.)\n// where each cold start creates a fresh module scope, resetting the Map. For production serverless use,\n// replace with a persistent store like Redis, Upstash, Vercel KV, or a distributed rate limiting service.\nconst rateLimitMap = new Map<string, { count: number; resetAt: number }>();\n\nfunction checkRateLimit(ip: string, limit = 100, windowMs = 60000): boolean {\n const now = Date.now();\n const record = rateLimitMap.get(ip);\n\n if (!record || now > record.resetAt) {\n rateLimitMap.set(ip, { count: 1, resetAt: now + windowMs });\n return true;\n }\n\n if (record.count >= limit) {\n return false;\n }\n\n record.count++;\n return true;\n}\n\nexport function specnavMiddleware(request: NextRequest): NextResponse | null {\n const isSpecnav = request.headers.get(\"x-specnav\") === \"1\";\n\n if (!isSpecnav) return null;\n\n // Rate limiting\n const ip = request.headers.get(\"x-forwarded-for\")?.split(\",\")[0] || \n request.headers.get(\"x-real-ip\") || \n \"unknown\";\n if (!checkRateLimit(ip)) {\n return new NextResponse(\"Too Many Requests\", { status: 429 });\n }\n\n // Security: Verify same-origin (deny if headers missing)\n const origin = request.headers.get(\"origin\");\n const referer = request.headers.get(\"referer\");\n \n if (!origin && !referer) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n \n const requestOrigin = origin || new URL(referer!).origin;\n const serverOrigin = new URL(request.url).origin;\n \n if (requestOrigin !== serverOrigin) {\n return new NextResponse(\"Forbidden\", { status: 403 });\n }\n\n // Clone request and add internal header for route handlers\n const headers = new Headers(request.headers);\n headers.set(\"x-specnav-partial\", \"1\");\n\n return NextResponse.next({\n request: {\n headers,\n },\n });\n}\n\nexport function isSpecnavRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav\") === \"1\";\n}\n\nexport function isPartialRequest(request: NextRequest): boolean {\n return request.headers.get(\"x-specnav-partial\") === \"1\";\n}\n"]}
|