@spring-systems/server 0.8.6 → 0.8.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.
Files changed (40) hide show
  1. package/dist/api-route-handler.js +1 -19
  2. package/dist/chunk-E4AX7MID.js +1 -0
  3. package/dist/chunk-FXUI75TW.js +1 -0
  4. package/dist/chunk-KEEIJ5EV.js +1 -0
  5. package/dist/chunk-M23YQYLU.js +1 -0
  6. package/dist/chunk-TVAJDSYB.js +1 -0
  7. package/dist/chunk-UDRRRHFI.js +1 -0
  8. package/dist/chunk-VB7DEUGT.js +1 -0
  9. package/dist/client.js +1 -14
  10. package/dist/handlers/index.js +1 -48
  11. package/dist/index.js +1 -44
  12. package/dist/next-adapters.js +1 -14
  13. package/dist/proxy-middleware.js +1 -10
  14. package/dist/rate-limiter.js +1 -15
  15. package/dist/runtime-env.js +1 -9
  16. package/dist/security-headers.js +1 -11
  17. package/package.json +109 -108
  18. package/dist/api-route-handler.js.map +0 -1
  19. package/dist/chunk-4SUIIQDW.js +0 -158
  20. package/dist/chunk-4SUIIQDW.js.map +0 -1
  21. package/dist/chunk-7IUSTA5W.js +0 -113
  22. package/dist/chunk-7IUSTA5W.js.map +0 -1
  23. package/dist/chunk-CP33WQ5Q.js +0 -47
  24. package/dist/chunk-CP33WQ5Q.js.map +0 -1
  25. package/dist/chunk-KA7RJCWA.js +0 -24
  26. package/dist/chunk-KA7RJCWA.js.map +0 -1
  27. package/dist/chunk-NFJ25NQQ.js +0 -377
  28. package/dist/chunk-NFJ25NQQ.js.map +0 -1
  29. package/dist/chunk-PZWKMIA4.js +0 -513
  30. package/dist/chunk-PZWKMIA4.js.map +0 -1
  31. package/dist/chunk-YV6DZVPI.js +0 -43
  32. package/dist/chunk-YV6DZVPI.js.map +0 -1
  33. package/dist/client.js.map +0 -1
  34. package/dist/handlers/index.js.map +0 -1
  35. package/dist/index.js.map +0 -1
  36. package/dist/next-adapters.js.map +0 -1
  37. package/dist/proxy-middleware.js.map +0 -1
  38. package/dist/rate-limiter.js.map +0 -1
  39. package/dist/runtime-env.js.map +0 -1
  40. package/dist/security-headers.js.map +0 -1
@@ -1,158 +0,0 @@
1
- import {
2
- BASE_SECURITY_HEADER_VALUES
3
- } from "./chunk-KA7RJCWA.js";
4
-
5
- // src/proxy-middleware.ts
6
- import { getFrameworkConfig } from "@spring-systems/core/config";
7
- import { NextResponse } from "next/server.js";
8
- function toOriginSource(value) {
9
- const raw = value.trim();
10
- if (!raw) return [];
11
- try {
12
- const parsed = new URL(raw);
13
- const out = [parsed.origin];
14
- if (parsed.protocol === "https:") {
15
- out.push(`wss://${parsed.host}`);
16
- } else if (parsed.protocol === "http:") {
17
- out.push(`ws://${parsed.host}`);
18
- }
19
- return out;
20
- } catch {
21
- return [];
22
- }
23
- }
24
- var BLOCKED_CSP_SCHEMES = /* @__PURE__ */ new Set(["javascript:", "data:", "blob:", "vbscript:", "filesystem:"]);
25
- function parseCspSource(token) {
26
- const value = token.trim();
27
- if (!value) return null;
28
- if (/[\s;,\r\n]/.test(value)) return null;
29
- if (/^[a-zA-Z][a-zA-Z0-9+.-]*:$/.test(value)) {
30
- const lower = value.toLowerCase();
31
- if (BLOCKED_CSP_SCHEMES.has(lower)) return null;
32
- return lower;
33
- }
34
- try {
35
- const parsed = new URL(value);
36
- if (!["http:", "https:", "ws:", "wss:"].includes(parsed.protocol)) return null;
37
- if (parsed.username || parsed.password) return null;
38
- if (parsed.pathname !== "/" || parsed.search || parsed.hash) return null;
39
- return `${parsed.protocol}//${parsed.host}`;
40
- } catch {
41
- return null;
42
- }
43
- }
44
- function parseReportUri(value) {
45
- const trimmed = value.trim();
46
- if (!trimmed) return null;
47
- if (/[\s;,\r\n"]/.test(trimmed)) return null;
48
- try {
49
- const parsed = new URL(trimmed);
50
- if (!["http:", "https:"].includes(parsed.protocol)) return null;
51
- if (parsed.username || parsed.password) return null;
52
- return parsed.toString();
53
- } catch {
54
- return null;
55
- }
56
- }
57
- function parseCspList(value) {
58
- const parsed = value.split(",").map((s) => parseCspSource(s)).filter((s) => !!s);
59
- return [...new Set(parsed)];
60
- }
61
- function proxy(req) {
62
- const url = req.nextUrl.clone();
63
- const TARGET_ENV = process.env.TARGET_ENV || "test";
64
- const isProdRuntime = TARGET_ENV === "prod" || process.env.NODE_ENV === "production";
65
- const isTargetProd = TARGET_ENV === "prod";
66
- const isLocalHost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
67
- const nonce = crypto.randomUUID().replace(/-/g, "");
68
- const denyStyleAttr = process.env.CSP_DENY_STYLE_ATTR === "true";
69
- const allowUnsafeInlineStyle = !isTargetProd;
70
- const connectHostsEnv = parseCspList(process.env.CSP_CONNECT_HOSTS || "");
71
- const apiConnectSources = toOriginSource(process.env.API_URL || "");
72
- const imgHostsEnv = parseCspList(process.env.CSP_IMG_HOSTS || "");
73
- const scriptHostsEnv = parseCspList(process.env.CSP_SCRIPT_HOSTS || "");
74
- const frameHostsEnv = parseCspList(process.env.CSP_FRAME_HOSTS || "");
75
- const cspConfig = getFrameworkConfig().csp;
76
- const validatedConnectSources = cspConfig.thirdPartyConnectSources.map((s) => parseCspSource(s)).filter((s) => !!s);
77
- const validatedImgSources = cspConfig.thirdPartyImageSources.map((s) => parseCspSource(s)).filter((s) => !!s);
78
- const validatedScriptSources = cspConfig.thirdPartyScriptSources.map((s) => parseCspSource(s)).filter((s) => !!s);
79
- const validatedFrameSources = cspConfig.thirdPartyFrameSources.map((s) => parseCspSource(s)).filter((s) => !!s);
80
- const connectSrc = [
81
- "'self'",
82
- ...validatedConnectSources,
83
- ...apiConnectSources,
84
- ...connectHostsEnv,
85
- ...!isProdRuntime ? ["http://localhost:*", "http://127.0.0.1:*", "ws://localhost:*", "ws://127.0.0.1:*"] : []
86
- ].join(" ");
87
- const imgSrc = ["'self'", "data:", "blob:", ...validatedImgSources, ...imgHostsEnv].join(" ");
88
- const scriptSrcHosts = [...validatedScriptSources, ...scriptHostsEnv].join(" ");
89
- const frameSrcHosts = [...validatedFrameSources, ...frameHostsEnv].join(" ");
90
- const reportUri = parseReportUri(process.env.CSP_REPORT_URI || "");
91
- const cspDirectives = [
92
- "default-src 'self'",
93
- "base-uri 'self'",
94
- "object-src 'none'",
95
- `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${scriptSrcHosts}`,
96
- `script-src-elem 'self' 'nonce-${nonce}' 'strict-dynamic' ${scriptSrcHosts}`,
97
- "script-src-attr 'none'",
98
- allowUnsafeInlineStyle ? "style-src 'self' 'unsafe-inline'" : `style-src 'self' 'nonce-${nonce}'`,
99
- denyStyleAttr ? "style-src-attr 'none'" : "style-src-attr 'unsafe-inline'",
100
- `img-src ${imgSrc}`,
101
- "media-src 'self' blob: data:",
102
- "manifest-src 'self'",
103
- `connect-src ${connectSrc}`,
104
- `frame-src ${frameSrcHosts || "'none'"}`,
105
- "frame-ancestors 'none'",
106
- "form-action 'self'",
107
- ...isTargetProd ? ["upgrade-insecure-requests"] : [],
108
- ...reportUri ? [`report-uri ${reportUri}`, "report-to csp-violations"] : []
109
- ];
110
- const csp = cspDirectives.join("; ");
111
- const blockedPrefixes = getFrameworkConfig().proxy.blockedPathPrefixesInProd ?? [];
112
- if (isProdRuntime) {
113
- for (const prefix of blockedPrefixes) {
114
- if (url.pathname.startsWith(prefix)) {
115
- const res2 = new NextResponse("Not Found", { status: 404 });
116
- return setSecurity(res2, { isLocalHost, isTargetProd, csp, nonce, reportUri });
117
- }
118
- }
119
- }
120
- if (url.pathname === "/") {
121
- const dest = new URL(url.toString());
122
- if (!isLocalHost && isTargetProd) dest.protocol = "https:";
123
- dest.pathname = getFrameworkConfig().app.defaultRoute;
124
- dest.search = "";
125
- const res2 = makeRedirect(dest, 308);
126
- return setSecurity(res2, { isLocalHost, isTargetProd, csp, nonce, reportUri });
127
- }
128
- const requestHeaders = new Headers(req.headers);
129
- requestHeaders.set("x-nonce", nonce);
130
- const res = NextResponse.next({ request: { headers: requestHeaders } });
131
- return setSecurity(res, { isLocalHost, isTargetProd, csp, nonce, reportUri });
132
- }
133
- function setSecurity(res, opts) {
134
- const { isLocalHost, csp, nonce, reportUri } = opts;
135
- if (csp) res.headers.set("Content-Security-Policy", csp);
136
- for (const [key, value] of Object.entries(BASE_SECURITY_HEADER_VALUES)) {
137
- res.headers.set(key, value);
138
- }
139
- if (opts.isTargetProd && !isLocalHost)
140
- res.headers.set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload");
141
- if (reportUri) res.headers.set("Reporting-Endpoints", `csp-violations="${reportUri}"`);
142
- if (nonce) res.headers.set("x-nonce", nonce);
143
- return res;
144
- }
145
- function makeRedirect(to, status = 308) {
146
- const res = new NextResponse(null, { status });
147
- res.headers.set("Location", typeof to === "string" ? to : to.toString());
148
- return res;
149
- }
150
- var proxyConfig = {
151
- matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|api/health).*)"]
152
- };
153
-
154
- export {
155
- proxy,
156
- proxyConfig
157
- };
158
- //# sourceMappingURL=chunk-4SUIIQDW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/proxy-middleware.ts"],"sourcesContent":["import { getFrameworkConfig } from \"@spring-systems/core/config\";\nimport { type NextRequest, NextResponse } from \"next/server.js\";\n\nimport { BASE_SECURITY_HEADER_VALUES } from \"./security-headers\";\n\nfunction toOriginSource(value: string): string[] {\n const raw = value.trim();\n if (!raw) return [];\n\n try {\n const parsed = new URL(raw);\n const out = [parsed.origin];\n\n if (parsed.protocol === \"https:\") {\n out.push(`wss://${parsed.host}`);\n } else if (parsed.protocol === \"http:\") {\n out.push(`ws://${parsed.host}`);\n }\n\n return out;\n } catch {\n return [];\n }\n}\n\nconst BLOCKED_CSP_SCHEMES = new Set([\"javascript:\", \"data:\", \"blob:\", \"vbscript:\", \"filesystem:\"]);\n\nfunction parseCspSource(token: string): string | null {\n const value = token.trim();\n if (!value) return null;\n\n // Prevent header/CSP injection via env values.\n if (/[\\s;,\\r\\n]/.test(value)) return null;\n\n // Allow safe scheme sources (e.g. https:, wss:). Block dangerous schemes\n // that enable XSS or data exfiltration when used in CSP directives.\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:$/.test(value)) {\n const lower = value.toLowerCase();\n if (BLOCKED_CSP_SCHEMES.has(lower)) return null;\n return lower;\n }\n\n try {\n const parsed = new URL(value);\n if (![\"http:\", \"https:\", \"ws:\", \"wss:\"].includes(parsed.protocol)) return null;\n if (parsed.username || parsed.password) return null;\n if (parsed.pathname !== \"/\" || parsed.search || parsed.hash) return null;\n return `${parsed.protocol}//${parsed.host}`;\n } catch {\n return null;\n }\n}\n\nfunction parseReportUri(value: string): string | null {\n const trimmed = value.trim();\n if (!trimmed) return null;\n // Block header injection characters and double-quotes (used in Reporting-Endpoints structured header).\n if (/[\\s;,\\r\\n\"]/.test(trimmed)) return null;\n try {\n const parsed = new URL(trimmed);\n if (![\"http:\", \"https:\"].includes(parsed.protocol)) return null;\n if (parsed.username || parsed.password) return null;\n return parsed.toString();\n } catch {\n return null;\n }\n}\n\nfunction parseCspList(value: string): string[] {\n const parsed = value\n .split(\",\")\n .map((s) => parseCspSource(s))\n .filter((s): s is string => !!s);\n return [...new Set(parsed)];\n}\n\nexport function proxy(req: NextRequest) {\n const url = req.nextUrl.clone();\n\n const TARGET_ENV = process.env.TARGET_ENV || \"test\";\n const isProdRuntime = TARGET_ENV === \"prod\" || process.env.NODE_ENV === \"production\";\n const isTargetProd = TARGET_ENV === \"prod\";\n\n const isLocalHost = url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\";\n\n const nonce = crypto.randomUUID().replace(/-/g, \"\");\n const denyStyleAttr = process.env.CSP_DENY_STYLE_ATTR === \"true\";\n const allowUnsafeInlineStyle = !isTargetProd;\n\n const connectHostsEnv = parseCspList(process.env.CSP_CONNECT_HOSTS || \"\");\n const apiConnectSources = toOriginSource(process.env.API_URL || \"\");\n const imgHostsEnv = parseCspList(process.env.CSP_IMG_HOSTS || \"\");\n const scriptHostsEnv = parseCspList(process.env.CSP_SCRIPT_HOSTS || \"\");\n const frameHostsEnv = parseCspList(process.env.CSP_FRAME_HOSTS || \"\");\n\n const cspConfig = getFrameworkConfig().csp;\n\n // Validate framework config CSP sources through parseCspSource to prevent injection\n const validatedConnectSources = cspConfig.thirdPartyConnectSources\n .map((s) => parseCspSource(s))\n .filter((s): s is string => !!s);\n const validatedImgSources = cspConfig.thirdPartyImageSources\n .map((s) => parseCspSource(s))\n .filter((s): s is string => !!s);\n const validatedScriptSources = cspConfig.thirdPartyScriptSources\n .map((s) => parseCspSource(s))\n .filter((s): s is string => !!s);\n const validatedFrameSources = cspConfig.thirdPartyFrameSources\n .map((s) => parseCspSource(s))\n .filter((s): s is string => !!s);\n\n const connectSrc = [\n \"'self'\",\n ...validatedConnectSources,\n ...apiConnectSources,\n ...connectHostsEnv,\n ...(!isProdRuntime ? [\"http://localhost:*\", \"http://127.0.0.1:*\", \"ws://localhost:*\", \"ws://127.0.0.1:*\"] : []),\n ].join(\" \");\n\n const imgSrc = [\"'self'\", \"data:\", \"blob:\", ...validatedImgSources, ...imgHostsEnv].join(\" \");\n\n const scriptSrcHosts = [...validatedScriptSources, ...scriptHostsEnv].join(\" \");\n const frameSrcHosts = [...validatedFrameSources, ...frameHostsEnv].join(\" \");\n\n const reportUri = parseReportUri(process.env.CSP_REPORT_URI || \"\");\n\n const cspDirectives = [\n \"default-src 'self'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${scriptSrcHosts}`,\n `script-src-elem 'self' 'nonce-${nonce}' 'strict-dynamic' ${scriptSrcHosts}`,\n \"script-src-attr 'none'\",\n allowUnsafeInlineStyle ? \"style-src 'self' 'unsafe-inline'\" : `style-src 'self' 'nonce-${nonce}'`,\n denyStyleAttr ? \"style-src-attr 'none'\" : \"style-src-attr 'unsafe-inline'\",\n `img-src ${imgSrc}`,\n \"media-src 'self' blob: data:\",\n \"manifest-src 'self'\",\n `connect-src ${connectSrc}`,\n `frame-src ${frameSrcHosts || \"'none'\"}`,\n \"frame-ancestors 'none'\",\n \"form-action 'self'\",\n ...(isTargetProd ? [\"upgrade-insecure-requests\"] : []),\n ...(reportUri ? [`report-uri ${reportUri}`, \"report-to csp-violations\"] : []),\n ];\n const csp = cspDirectives.join(\"; \");\n\n const blockedPrefixes = getFrameworkConfig().proxy.blockedPathPrefixesInProd ?? [];\n if (isProdRuntime) {\n for (const prefix of blockedPrefixes) {\n if (url.pathname.startsWith(prefix)) {\n const res = new NextResponse(\"Not Found\", { status: 404 });\n return setSecurity(res, { isLocalHost, isTargetProd, csp, nonce, reportUri });\n }\n }\n }\n\n if (url.pathname === \"/\") {\n const dest = new URL(url.toString());\n if (!isLocalHost && isTargetProd) dest.protocol = \"https:\";\n dest.pathname = getFrameworkConfig().app.defaultRoute;\n dest.search = \"\";\n\n const res = makeRedirect(dest, 308);\n return setSecurity(res, { isLocalHost, isTargetProd, csp, nonce, reportUri });\n }\n\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set(\"x-nonce\", nonce);\n\n const res = NextResponse.next({ request: { headers: requestHeaders } });\n return setSecurity(res, { isLocalHost, isTargetProd, csp, nonce, reportUri });\n}\n\nfunction setSecurity(\n res: NextResponse,\n opts: { isLocalHost: boolean; isTargetProd: boolean; csp?: string; nonce?: string; reportUri?: string | null },\n) {\n const { isLocalHost, csp, nonce, reportUri } = opts;\n if (csp) res.headers.set(\"Content-Security-Policy\", csp);\n for (const [key, value] of Object.entries(BASE_SECURITY_HEADER_VALUES)) {\n res.headers.set(key, value);\n }\n if (opts.isTargetProd && !isLocalHost)\n res.headers.set(\"Strict-Transport-Security\", \"max-age=63072000; includeSubDomains; preload\");\n if (reportUri) res.headers.set(\"Reporting-Endpoints\", `csp-violations=\"${reportUri}\"`);\n if (nonce) res.headers.set(\"x-nonce\", nonce);\n return res;\n}\n\nfunction makeRedirect(to: URL | string, status = 308) {\n const res = new NextResponse(null, { status });\n res.headers.set(\"Location\", typeof to === \"string\" ? to : to.toString());\n return res;\n}\n\nexport const proxyConfig = {\n matcher: [\"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|api/health).*)\"],\n};\n"],"mappings":"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","names":["res"]}
@@ -1,113 +0,0 @@
1
- // src/rate-limiter.ts
2
- var InMemoryRateLimiter = class {
3
- ipStore = /* @__PURE__ */ new Map();
4
- accountStore = /* @__PURE__ */ new Map();
5
- getStore(store) {
6
- return store === "ip" ? this.ipStore : this.accountStore;
7
- }
8
- get(store, key) {
9
- return this.getStore(store).get(key) ?? null;
10
- }
11
- set(store, key, entry) {
12
- this.getStore(store).set(key, entry);
13
- }
14
- delete(store, key) {
15
- this.getStore(store).delete(key);
16
- }
17
- size(store) {
18
- return this.getStore(store).size;
19
- }
20
- evictOldest(store, count) {
21
- const map = this.getStore(store);
22
- const entries = [...map.entries()].sort((a, b) => a[1].windowStartedAt - b[1].windowStartedAt);
23
- for (let i = 0; i < count && i < entries.length; i++) {
24
- const candidate = entries[i];
25
- if (!candidate) break;
26
- map.delete(candidate[0]);
27
- }
28
- }
29
- sweepExpired(store, now, windowMs) {
30
- const map = this.getStore(store);
31
- for (const [key, entry] of map.entries()) {
32
- const windowExpired = now - entry.windowStartedAt > windowMs;
33
- const noActiveBlock = entry.blockedUntil <= now;
34
- if (windowExpired && noActiveBlock) {
35
- map.delete(key);
36
- }
37
- }
38
- }
39
- };
40
- var adapter = new InMemoryRateLimiter();
41
- var hasWarnedInMemory = false;
42
- function setRateLimiterAdapter(custom) {
43
- adapter = custom;
44
- }
45
- function getRateLimiterAdapter() {
46
- return adapter;
47
- }
48
- function checkRateLimit(ipKey, accountKey, policy) {
49
- if (!hasWarnedInMemory && adapter instanceof InMemoryRateLimiter && process.env.NODE_ENV === "production") {
50
- hasWarnedInMemory = true;
51
- console.warn("[server] Rate limiter is using in-memory storage in production. For multi-instance deployments, call setRateLimiterAdapter() with a shared-storage adapter.");
52
- }
53
- const now = Date.now();
54
- const ipResult = checkSingleLimit(adapter, "ip", ipKey, policy.maxAttemptsByIpAndAccount, policy, now);
55
- if (ipResult) return ipResult;
56
- if (accountKey) {
57
- const accountResult = checkSingleLimit(adapter, "account", accountKey, policy.maxAttemptsByAccount, policy, now);
58
- if (accountResult) return accountResult;
59
- }
60
- return null;
61
- }
62
- function recordFailedAttempt(ipKey, accountKey, policy) {
63
- const now = Date.now();
64
- recordAttempt(adapter, "ip", ipKey, policy, now);
65
- if (accountKey) {
66
- recordAttempt(adapter, "account", accountKey, policy, now);
67
- }
68
- }
69
- function clearRateLimitEntries(ipKey, accountKey) {
70
- adapter.delete("ip", ipKey);
71
- if (accountKey) {
72
- adapter.delete("account", accountKey);
73
- }
74
- }
75
- function checkSingleLimit(adap, store, key, maxAttempts, policy, now) {
76
- const entry = adap.get(store, key);
77
- if (!entry) return null;
78
- if (entry.blockedUntil > now) {
79
- const remainSec = Math.ceil((entry.blockedUntil - now) / 1e3);
80
- return `Rate limited (${store}). Try again in ${remainSec}s.`;
81
- }
82
- if (now - entry.windowStartedAt > policy.windowMs) {
83
- adap.delete(store, key);
84
- return null;
85
- }
86
- if (entry.count >= maxAttempts) {
87
- entry.blockedUntil = now + policy.blockMs;
88
- adap.set(store, key, entry);
89
- return `Too many attempts (${store}). Blocked for ${Math.ceil(policy.blockMs / 1e3)}s.`;
90
- }
91
- return null;
92
- }
93
- function recordAttempt(adap, store, key, policy, now) {
94
- if (adap.size(store) >= policy.maxKeys) {
95
- adap.evictOldest(store, Math.floor(policy.maxKeys * 0.1));
96
- }
97
- const entry = adap.get(store, key);
98
- if (!entry || now - entry.windowStartedAt > policy.windowMs) {
99
- adap.set(store, key, { count: 1, windowStartedAt: now, blockedUntil: 0 });
100
- } else {
101
- entry.count++;
102
- adap.set(store, key, entry);
103
- }
104
- }
105
-
106
- export {
107
- setRateLimiterAdapter,
108
- getRateLimiterAdapter,
109
- checkRateLimit,
110
- recordFailedAttempt,
111
- clearRateLimitEntries
112
- };
113
- //# sourceMappingURL=chunk-7IUSTA5W.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/rate-limiter.ts"],"sourcesContent":["/**\n * Pluggable rate limiter for API proxy authentication.\n *\n * The default implementation uses in-memory Maps (suitable for single-instance deployments).\n * Multi-instance deployments can replace this with a custom adapter via `setRateLimiterAdapter()`.\n *\n * @example\n * ```ts\n * import { setRateLimiterAdapter } from \"@spring-systems/server/rate-limiter\";\n * import { createCustomRateLimiter } from \"./my-rate-limiter\";\n *\n * setRateLimiterAdapter(createCustomRateLimiter());\n * ```\n *\n * @module rate-limiter\n */\n\nexport interface RateLimitEntry {\n count: number;\n windowStartedAt: number;\n blockedUntil: number;\n}\n\nexport interface RateLimitPolicy {\n windowMs: number;\n blockMs: number;\n maxAttemptsByIpAndAccount: number;\n maxAttemptsByAccount: number;\n maxKeys: number;\n}\n\n/**\n * Rate limiter adapter interface. Implementations must be async-safe.\n */\nexport interface RateLimiterAdapter {\n /** Get the current rate limit entry for a key, or null if no entry exists. */\n get(store: \"ip\" | \"account\", key: string): RateLimitEntry | null;\n\n /** Set/update the rate limit entry for a key. */\n set(store: \"ip\" | \"account\", key: string, entry: RateLimitEntry): void;\n\n /** Delete an entry (e.g. on successful login). */\n delete(store: \"ip\" | \"account\", key: string): void;\n\n /** Get the total number of tracked keys (for eviction logic). */\n size(store: \"ip\" | \"account\"): number;\n\n /** Clear the oldest entries when maxKeys is exceeded. */\n evictOldest(store: \"ip\" | \"account\", count: number): void;\n\n /**\n * Remove expired entries for a store.\n * Optional to preserve compatibility with existing custom adapters.\n */\n sweepExpired?(store: \"ip\" | \"account\", now: number, windowMs: number): void;\n}\n\n// ---------------------------------------------------------------------------\n// Default in-memory implementation\n// ---------------------------------------------------------------------------\n\nclass InMemoryRateLimiter implements RateLimiterAdapter {\n private ipStore = new Map<string, RateLimitEntry>();\n private accountStore = new Map<string, RateLimitEntry>();\n\n private getStore(store: \"ip\" | \"account\"): Map<string, RateLimitEntry> {\n return store === \"ip\" ? this.ipStore : this.accountStore;\n }\n\n get(store: \"ip\" | \"account\", key: string): RateLimitEntry | null {\n return this.getStore(store).get(key) ?? null;\n }\n\n set(store: \"ip\" | \"account\", key: string, entry: RateLimitEntry): void {\n this.getStore(store).set(key, entry);\n }\n\n delete(store: \"ip\" | \"account\", key: string): void {\n this.getStore(store).delete(key);\n }\n\n size(store: \"ip\" | \"account\"): number {\n return this.getStore(store).size;\n }\n\n evictOldest(store: \"ip\" | \"account\", count: number): void {\n const map = this.getStore(store);\n const entries = [...map.entries()].sort((a, b) => a[1].windowStartedAt - b[1].windowStartedAt);\n for (let i = 0; i < count && i < entries.length; i++) {\n const candidate = entries[i];\n if (!candidate) break;\n map.delete(candidate[0]);\n }\n }\n\n sweepExpired(store: \"ip\" | \"account\", now: number, windowMs: number): void {\n const map = this.getStore(store);\n for (const [key, entry] of map.entries()) {\n const windowExpired = now - entry.windowStartedAt > windowMs;\n const noActiveBlock = entry.blockedUntil <= now;\n if (windowExpired && noActiveBlock) {\n map.delete(key);\n }\n }\n }\n}\n\n// Singleton adapter\nlet adapter: RateLimiterAdapter = new InMemoryRateLimiter();\nlet hasWarnedInMemory = false;\n\n/** Replace the default in-memory rate limiter with a custom adapter. */\nexport function setRateLimiterAdapter(custom: RateLimiterAdapter): void {\n adapter = custom;\n}\n\n/** Get the current rate limiter adapter. */\nexport function getRateLimiterAdapter(): RateLimiterAdapter {\n return adapter;\n}\n\n/**\n * Check if a login attempt should be rate-limited.\n * @returns A reason string if blocked, or null if allowed.\n */\nexport function checkRateLimit(\n ipKey: string,\n accountKey: string | null,\n policy: RateLimitPolicy,\n): string | null {\n if (!hasWarnedInMemory && adapter instanceof InMemoryRateLimiter && process.env.NODE_ENV === \"production\") {\n hasWarnedInMemory = true;\n console.warn(\"[server] Rate limiter is using in-memory storage in production. For multi-instance deployments, call setRateLimiterAdapter() with a shared-storage adapter.\");\n }\n const now = Date.now();\n\n // Check IP-based limit\n const ipResult = checkSingleLimit(adapter, \"ip\", ipKey, policy.maxAttemptsByIpAndAccount, policy, now);\n if (ipResult) return ipResult;\n\n // Check account-based limit\n if (accountKey) {\n const accountResult = checkSingleLimit(adapter, \"account\", accountKey, policy.maxAttemptsByAccount, policy, now);\n if (accountResult) return accountResult;\n }\n\n return null;\n}\n\n/**\n * Record a failed login attempt.\n */\nexport function recordFailedAttempt(\n ipKey: string,\n accountKey: string | null,\n policy: RateLimitPolicy,\n): void {\n const now = Date.now();\n recordAttempt(adapter, \"ip\", ipKey, policy, now);\n if (accountKey) {\n recordAttempt(adapter, \"account\", accountKey, policy, now);\n }\n}\n\n/**\n * Clear rate limit entries for a key (e.g. on successful login).\n */\nexport function clearRateLimitEntries(ipKey: string, accountKey: string | null): void {\n adapter.delete(\"ip\", ipKey);\n if (accountKey) {\n adapter.delete(\"account\", accountKey);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction checkSingleLimit(\n adap: RateLimiterAdapter,\n store: \"ip\" | \"account\",\n key: string,\n maxAttempts: number,\n policy: RateLimitPolicy,\n now: number,\n): string | null {\n const entry = adap.get(store, key);\n if (!entry) return null;\n\n if (entry.blockedUntil > now) {\n const remainSec = Math.ceil((entry.blockedUntil - now) / 1000);\n return `Rate limited (${store}). Try again in ${remainSec}s.`;\n }\n\n if (now - entry.windowStartedAt > policy.windowMs) {\n adap.delete(store, key);\n return null;\n }\n\n if (entry.count >= maxAttempts) {\n entry.blockedUntil = now + policy.blockMs;\n adap.set(store, key, entry);\n return `Too many attempts (${store}). Blocked for ${Math.ceil(policy.blockMs / 1000)}s.`;\n }\n\n return null;\n}\n\nfunction recordAttempt(\n adap: RateLimiterAdapter,\n store: \"ip\" | \"account\",\n key: string,\n policy: RateLimitPolicy,\n now: number,\n): void {\n // Evict old entries if needed\n if (adap.size(store) >= policy.maxKeys) {\n adap.evictOldest(store, Math.floor(policy.maxKeys * 0.1));\n }\n\n const entry = adap.get(store, key);\n if (!entry || now - entry.windowStartedAt > policy.windowMs) {\n adap.set(store, key, { count: 1, windowStartedAt: now, blockedUntil: 0 });\n } else {\n entry.count++;\n adap.set(store, key, entry);\n }\n}\n"],"mappings":";AA6DA,IAAM,sBAAN,MAAwD;AAAA,EAC5C,UAAU,oBAAI,IAA4B;AAAA,EAC1C,eAAe,oBAAI,IAA4B;AAAA,EAE/C,SAAS,OAAsD;AACnE,WAAO,UAAU,OAAO,KAAK,UAAU,KAAK;AAAA,EAChD;AAAA,EAEA,IAAI,OAAyB,KAAoC;AAC7D,WAAO,KAAK,SAAS,KAAK,EAAE,IAAI,GAAG,KAAK;AAAA,EAC5C;AAAA,EAEA,IAAI,OAAyB,KAAa,OAA6B;AACnE,SAAK,SAAS,KAAK,EAAE,IAAI,KAAK,KAAK;AAAA,EACvC;AAAA,EAEA,OAAO,OAAyB,KAAmB;AAC/C,SAAK,SAAS,KAAK,EAAE,OAAO,GAAG;AAAA,EACnC;AAAA,EAEA,KAAK,OAAiC;AAClC,WAAO,KAAK,SAAS,KAAK,EAAE;AAAA,EAChC;AAAA,EAEA,YAAY,OAAyB,OAAqB;AACtD,UAAM,MAAM,KAAK,SAAS,KAAK;AAC/B,UAAM,UAAU,CAAC,GAAG,IAAI,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,eAAe;AAC7F,aAAS,IAAI,GAAG,IAAI,SAAS,IAAI,QAAQ,QAAQ,KAAK;AAClD,YAAM,YAAY,QAAQ,CAAC;AAC3B,UAAI,CAAC,UAAW;AAChB,UAAI,OAAO,UAAU,CAAC,CAAC;AAAA,IAC3B;AAAA,EACJ;AAAA,EAEA,aAAa,OAAyB,KAAa,UAAwB;AACvE,UAAM,MAAM,KAAK,SAAS,KAAK;AAC/B,eAAW,CAAC,KAAK,KAAK,KAAK,IAAI,QAAQ,GAAG;AACtC,YAAM,gBAAgB,MAAM,MAAM,kBAAkB;AACpD,YAAM,gBAAgB,MAAM,gBAAgB;AAC5C,UAAI,iBAAiB,eAAe;AAChC,YAAI,OAAO,GAAG;AAAA,MAClB;AAAA,IACJ;AAAA,EACJ;AACJ;AAGA,IAAI,UAA8B,IAAI,oBAAoB;AAC1D,IAAI,oBAAoB;AAGjB,SAAS,sBAAsB,QAAkC;AACpE,YAAU;AACd;AAGO,SAAS,wBAA4C;AACxD,SAAO;AACX;AAMO,SAAS,eACZ,OACA,YACA,QACa;AACb,MAAI,CAAC,qBAAqB,mBAAmB,uBAAuB,QAAQ,IAAI,aAAa,cAAc;AACvG,wBAAoB;AACpB,YAAQ,KAAK,6JAA6J;AAAA,EAC9K;AACA,QAAM,MAAM,KAAK,IAAI;AAGrB,QAAM,WAAW,iBAAiB,SAAS,MAAM,OAAO,OAAO,2BAA2B,QAAQ,GAAG;AACrG,MAAI,SAAU,QAAO;AAGrB,MAAI,YAAY;AACZ,UAAM,gBAAgB,iBAAiB,SAAS,WAAW,YAAY,OAAO,sBAAsB,QAAQ,GAAG;AAC/G,QAAI,cAAe,QAAO;AAAA,EAC9B;AAEA,SAAO;AACX;AAKO,SAAS,oBACZ,OACA,YACA,QACI;AACJ,QAAM,MAAM,KAAK,IAAI;AACrB,gBAAc,SAAS,MAAM,OAAO,QAAQ,GAAG;AAC/C,MAAI,YAAY;AACZ,kBAAc,SAAS,WAAW,YAAY,QAAQ,GAAG;AAAA,EAC7D;AACJ;AAKO,SAAS,sBAAsB,OAAe,YAAiC;AAClF,UAAQ,OAAO,MAAM,KAAK;AAC1B,MAAI,YAAY;AACZ,YAAQ,OAAO,WAAW,UAAU;AAAA,EACxC;AACJ;AAMA,SAAS,iBACL,MACA,OACA,KACA,aACA,QACA,KACa;AACb,QAAM,QAAQ,KAAK,IAAI,OAAO,GAAG;AACjC,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,eAAe,KAAK;AAC1B,UAAM,YAAY,KAAK,MAAM,MAAM,eAAe,OAAO,GAAI;AAC7D,WAAO,iBAAiB,KAAK,mBAAmB,SAAS;AAAA,EAC7D;AAEA,MAAI,MAAM,MAAM,kBAAkB,OAAO,UAAU;AAC/C,SAAK,OAAO,OAAO,GAAG;AACtB,WAAO;AAAA,EACX;AAEA,MAAI,MAAM,SAAS,aAAa;AAC5B,UAAM,eAAe,MAAM,OAAO;AAClC,SAAK,IAAI,OAAO,KAAK,KAAK;AAC1B,WAAO,sBAAsB,KAAK,kBAAkB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;AAAA,EACxF;AAEA,SAAO;AACX;AAEA,SAAS,cACL,MACA,OACA,KACA,QACA,KACI;AAEJ,MAAI,KAAK,KAAK,KAAK,KAAK,OAAO,SAAS;AACpC,SAAK,YAAY,OAAO,KAAK,MAAM,OAAO,UAAU,GAAG,CAAC;AAAA,EAC5D;AAEA,QAAM,QAAQ,KAAK,IAAI,OAAO,GAAG;AACjC,MAAI,CAAC,SAAS,MAAM,MAAM,kBAAkB,OAAO,UAAU;AACzD,SAAK,IAAI,OAAO,KAAK,EAAE,OAAO,GAAG,iBAAiB,KAAK,cAAc,EAAE,CAAC;AAAA,EAC5E,OAAO;AACH,UAAM;AACN,SAAK,IAAI,OAAO,KAAK,KAAK;AAAA,EAC9B;AACJ;","names":[]}
@@ -1,47 +0,0 @@
1
- // src/next-adapters.ts
2
- import nextDynamic from "next/dynamic";
3
- import NextImage from "next/image";
4
- import NextLink from "next/link";
5
- import {
6
- useParams as useNextParams,
7
- usePathname as useNextPathname,
8
- useRouter as useNextRouter,
9
- useSearchParams as useNextSearchParams
10
- } from "next/navigation";
11
- var hasLoggedDefaultSsrWarning = false;
12
- function createNextRouteAdapter() {
13
- return {
14
- usePathname: useNextPathname,
15
- useSearchParams: () => useNextSearchParams() ?? new URLSearchParams(),
16
- useParams: () => useNextParams() ?? {},
17
- useRouter: useNextRouter
18
- };
19
- }
20
- function createNextUIAdapter(options) {
21
- const defaultDynamicSsr = options?.defaultDynamicSsr ?? true;
22
- if (options?.defaultDynamicSsr === void 0 && !hasLoggedDefaultSsrWarning && process.env.NODE_ENV !== "production") {
23
- hasLoggedDefaultSsrWarning = true;
24
- console.warn(
25
- "[server] createNextUIAdapter() defaults dynamic imports to ssr:true. For client-only apps use createClientOnlyNextUIAdapter() or pass { defaultDynamicSsr: false }."
26
- );
27
- }
28
- return {
29
- Image: NextImage,
30
- Link: NextLink,
31
- dynamic: (loader, opts) => nextDynamic(loader, { ssr: opts?.ssr ?? defaultDynamicSsr })
32
- };
33
- }
34
- function createClientOnlyNextUIAdapter() {
35
- return createNextUIAdapter({ defaultDynamicSsr: false });
36
- }
37
- function createSsrNextUIAdapter() {
38
- return createNextUIAdapter({ defaultDynamicSsr: true });
39
- }
40
-
41
- export {
42
- createNextRouteAdapter,
43
- createNextUIAdapter,
44
- createClientOnlyNextUIAdapter,
45
- createSsrNextUIAdapter
46
- };
47
- //# sourceMappingURL=chunk-CP33WQ5Q.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/next-adapters.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Next.js adapter presets for RouteAdapter and UIAdapter.\n * Consumer app calls these in Root.tsx / layout.tsx to register the Next.js implementations.\n * @module next-adapters\n */\n\nimport type { ImageProps, LinkProps, RouteAdapter, UIAdapter } from \"@spring-systems/ui/adapters\";\nimport nextDynamic from \"next/dynamic\";\nimport NextImage from \"next/image\";\nimport NextLink from \"next/link\";\nimport {\n useParams as useNextParams,\n usePathname as useNextPathname,\n useRouter as useNextRouter,\n useSearchParams as useNextSearchParams,\n} from \"next/navigation\";\nimport type React from \"react\";\n\nlet hasLoggedDefaultSsrWarning = false;\n\nexport interface NextUIAdapterOptions {\n /**\n * Default SSR behavior for `dynamic` when call-site does not provide `{ ssr }`.\n * Defaults to `true` for backward compatibility.\n */\n defaultDynamicSsr?: boolean;\n}\n\n/** Creates a RouteAdapter backed by Next.js App Router hooks. */\nexport function createNextRouteAdapter(): RouteAdapter {\n return {\n usePathname: useNextPathname,\n useSearchParams: () => useNextSearchParams() ?? new URLSearchParams(),\n useParams: () => (useNextParams() ?? {}) as Record<string, string | string[]>,\n useRouter: useNextRouter,\n };\n}\n\n/** Creates a UIAdapter backed by Next.js Image, Link, and dynamic imports. */\nexport function createNextUIAdapter(options?: NextUIAdapterOptions): UIAdapter {\n const defaultDynamicSsr = options?.defaultDynamicSsr ?? true;\n if (options?.defaultDynamicSsr === undefined && !hasLoggedDefaultSsrWarning && process.env.NODE_ENV !== \"production\") {\n hasLoggedDefaultSsrWarning = true;\n // Keeping backward-compatible default SSR behavior, but warn in dev to avoid accidental use in client-only apps.\n console.warn(\n \"[server] createNextUIAdapter() defaults dynamic imports to ssr:true. For client-only apps use createClientOnlyNextUIAdapter() or pass { defaultDynamicSsr: false }.\"\n );\n }\n return {\n Image: NextImage as React.ComponentType<ImageProps>,\n Link: NextLink as React.ComponentType<LinkProps>,\n dynamic: <T extends React.ComponentType<React.ComponentProps<T>>>(\n loader: () => Promise<{ default: T }>,\n opts?: { ssr?: boolean },\n ) => nextDynamic(loader, { ssr: opts?.ssr ?? defaultDynamicSsr }) as unknown as T,\n };\n}\n\n/** Creates a UIAdapter preset optimized for client-only apps (dynamic imports default to `ssr: false`). */\nexport function createClientOnlyNextUIAdapter(): UIAdapter {\n return createNextUIAdapter({ defaultDynamicSsr: false });\n}\n\n/** Creates a UIAdapter preset with SSR-enabled dynamic imports by default. */\nexport function createSsrNextUIAdapter(): UIAdapter {\n return createNextUIAdapter({ defaultDynamicSsr: true });\n}\n"],"mappings":";AASA,OAAO,iBAAiB;AACxB,OAAO,eAAe;AACtB,OAAO,cAAc;AACrB;AAAA,EACI,aAAa;AAAA,EACb,eAAe;AAAA,EACf,aAAa;AAAA,EACb,mBAAmB;AAAA,OAChB;AAGP,IAAI,6BAA6B;AAW1B,SAAS,yBAAuC;AACnD,SAAO;AAAA,IACH,aAAa;AAAA,IACb,iBAAiB,MAAM,oBAAoB,KAAK,IAAI,gBAAgB;AAAA,IACpE,WAAW,MAAO,cAAc,KAAK,CAAC;AAAA,IACtC,WAAW;AAAA,EACf;AACJ;AAGO,SAAS,oBAAoB,SAA2C;AAC3E,QAAM,oBAAoB,SAAS,qBAAqB;AACxD,MAAI,SAAS,sBAAsB,UAAa,CAAC,8BAA8B,QAAQ,IAAI,aAAa,cAAc;AAClH,iCAA6B;AAE7B,YAAQ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AAAA,IACH,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CACL,QACA,SACC,YAAY,QAAQ,EAAE,KAAK,MAAM,OAAO,kBAAkB,CAAC;AAAA,EACpE;AACJ;AAGO,SAAS,gCAA2C;AACvD,SAAO,oBAAoB,EAAE,mBAAmB,MAAM,CAAC;AAC3D;AAGO,SAAS,yBAAoC;AAChD,SAAO,oBAAoB,EAAE,mBAAmB,KAAK,CAAC;AAC1D;","names":[]}
@@ -1,24 +0,0 @@
1
- // src/security-headers.ts
2
- var PERMISSIONS_POLICY_VALUE = "accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()";
3
- var BASE_SECURITY_HEADER_VALUES = Object.freeze({
4
- "Referrer-Policy": "strict-origin-when-cross-origin",
5
- "X-Content-Type-Options": "nosniff",
6
- "X-Frame-Options": "DENY",
7
- "Cross-Origin-Opener-Policy": "same-origin",
8
- "Cross-Origin-Resource-Policy": "same-origin",
9
- "X-DNS-Prefetch-Control": "off",
10
- "X-Permitted-Cross-Domain-Policies": "none",
11
- "Origin-Agent-Cluster": "?1",
12
- "Permissions-Policy": PERMISSIONS_POLICY_VALUE
13
- });
14
- var BASE_SECURITY_HEADERS = Object.entries(BASE_SECURITY_HEADER_VALUES).map(([key, value]) => ({
15
- key,
16
- value
17
- }));
18
-
19
- export {
20
- PERMISSIONS_POLICY_VALUE,
21
- BASE_SECURITY_HEADER_VALUES,
22
- BASE_SECURITY_HEADERS
23
- };
24
- //# sourceMappingURL=chunk-KA7RJCWA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/security-headers.ts"],"sourcesContent":["export const PERMISSIONS_POLICY_VALUE =\n \"accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()\";\n\nexport const BASE_SECURITY_HEADER_VALUES: Readonly<Record<string, string>> = Object.freeze({\n \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"X-Frame-Options\": \"DENY\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Resource-Policy\": \"same-origin\",\n \"X-DNS-Prefetch-Control\": \"off\",\n \"X-Permitted-Cross-Domain-Policies\": \"none\",\n \"Origin-Agent-Cluster\": \"?1\",\n \"Permissions-Policy\": PERMISSIONS_POLICY_VALUE,\n});\n\nexport const BASE_SECURITY_HEADERS = Object.entries(BASE_SECURITY_HEADER_VALUES).map(([key, value]) => ({\n key,\n value,\n}));\n"],"mappings":";AAAO,IAAM,2BACT;AAEG,IAAM,8BAAgE,OAAO,OAAO;AAAA,EACvF,mBAAmB;AAAA,EACnB,0BAA0B;AAAA,EAC1B,mBAAmB;AAAA,EACnB,8BAA8B;AAAA,EAC9B,gCAAgC;AAAA,EAChC,0BAA0B;AAAA,EAC1B,qCAAqC;AAAA,EACrC,wBAAwB;AAAA,EACxB,sBAAsB;AAC1B,CAAC;AAEM,IAAM,wBAAwB,OAAO,QAAQ,2BAA2B,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,EACpG;AAAA,EACA;AACJ,EAAE;","names":[]}