@spring-systems/server 0.8.4 → 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 (41) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/api-route-handler.js +1 -19
  3. package/dist/chunk-E4AX7MID.js +1 -0
  4. package/dist/chunk-FXUI75TW.js +1 -0
  5. package/dist/chunk-KEEIJ5EV.js +1 -0
  6. package/dist/chunk-M23YQYLU.js +1 -0
  7. package/dist/chunk-TVAJDSYB.js +1 -0
  8. package/dist/chunk-UDRRRHFI.js +1 -0
  9. package/dist/chunk-VB7DEUGT.js +1 -0
  10. package/dist/client.js +1 -14
  11. package/dist/handlers/index.js +1 -48
  12. package/dist/index.js +1 -44
  13. package/dist/next-adapters.js +1 -14
  14. package/dist/proxy-middleware.js +1 -10
  15. package/dist/rate-limiter.js +1 -15
  16. package/dist/runtime-env.js +1 -9
  17. package/dist/security-headers.js +1 -11
  18. package/package.json +3 -3
  19. package/dist/api-route-handler.js.map +0 -1
  20. package/dist/chunk-4SUIIQDW.js +0 -158
  21. package/dist/chunk-4SUIIQDW.js.map +0 -1
  22. package/dist/chunk-7IUSTA5W.js +0 -113
  23. package/dist/chunk-7IUSTA5W.js.map +0 -1
  24. package/dist/chunk-CP33WQ5Q.js +0 -47
  25. package/dist/chunk-CP33WQ5Q.js.map +0 -1
  26. package/dist/chunk-KA7RJCWA.js +0 -24
  27. package/dist/chunk-KA7RJCWA.js.map +0 -1
  28. package/dist/chunk-NFJ25NQQ.js +0 -377
  29. package/dist/chunk-NFJ25NQQ.js.map +0 -1
  30. package/dist/chunk-PZWKMIA4.js +0 -513
  31. package/dist/chunk-PZWKMIA4.js.map +0 -1
  32. package/dist/chunk-YV6DZVPI.js +0 -43
  33. package/dist/chunk-YV6DZVPI.js.map +0 -1
  34. package/dist/client.js.map +0 -1
  35. package/dist/handlers/index.js.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/next-adapters.js.map +0 -1
  38. package/dist/proxy-middleware.js.map +0 -1
  39. package/dist/rate-limiter.js.map +0 -1
  40. package/dist/runtime-env.js.map +0 -1
  41. package/dist/security-headers.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @spring-systems/server
2
2
 
3
+ ## 0.8.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @spring-systems/ui@0.8.6
9
+ - @spring-systems/core@0.8.6
10
+
3
11
  ## 0.8.2
4
12
 
5
13
  ### Patch Changes
@@ -1,19 +1 @@
1
- import {
2
- DELETE,
3
- GET,
4
- OPTIONS,
5
- PATCH,
6
- POST,
7
- PUT
8
- } from "./chunk-NFJ25NQQ.js";
9
- import "./chunk-PZWKMIA4.js";
10
- import "./chunk-7IUSTA5W.js";
11
- export {
12
- DELETE,
13
- GET,
14
- OPTIONS,
15
- PATCH,
16
- POST,
17
- PUT
18
- };
19
- //# sourceMappingURL=api-route-handler.js.map
1
+ import{a,b,c,d,e,f}from"./chunk-VB7DEUGT.js";import"./chunk-M23YQYLU.js";import"./chunk-E4AX7MID.js";export{d as DELETE,a as GET,f as OPTIONS,e as PATCH,b as POST,c as PUT};
@@ -0,0 +1 @@
1
+ var s=class{ipStore=new Map;accountStore=new Map;getStore(t){return t==="ip"?this.ipStore:this.accountStore}get(t,e){return this.getStore(t).get(e)??null}set(t,e,r){this.getStore(t).set(e,r)}delete(t,e){this.getStore(t).delete(e)}size(t){return this.getStore(t).size}evictOldest(t,e){let r=this.getStore(t),o=[...r.entries()].sort((i,c)=>i[1].windowStartedAt-c[1].windowStartedAt);for(let i=0;i<e&&i<o.length;i++){let c=o[i];if(!c)break;r.delete(c[0])}}sweepExpired(t,e,r){let o=this.getStore(t);for(let[i,c]of o.entries()){let u=e-c.windowStartedAt>r,p=c.blockedUntil<=e;u&&p&&o.delete(i)}}},a=new s,l=!1;function g(n){a=n}function f(){return a}function w(n,t,e){!l&&a instanceof s&&process.env.NODE_ENV==="production"&&(l=!0,console.warn("[server] Rate limiter is using in-memory storage in production. For multi-instance deployments, call setRateLimiterAdapter() with a shared-storage adapter."));let r=Date.now(),o=d(a,"ip",n,e.maxAttemptsByIpAndAccount,e,r);if(o)return o;if(t){let i=d(a,"account",t,e.maxAttemptsByAccount,e,r);if(i)return i}return null}function A(n,t,e){let r=Date.now();m(a,"ip",n,e,r),t&&m(a,"account",t,e,r)}function b(n,t){a.delete("ip",n),t&&a.delete("account",t)}function d(n,t,e,r,o,i){let c=n.get(t,e);if(!c)return null;if(c.blockedUntil>i){let u=Math.ceil((c.blockedUntil-i)/1e3);return`Rate limited (${t}). Try again in ${u}s.`}return i-c.windowStartedAt>o.windowMs?(n.delete(t,e),null):c.count>=r?(c.blockedUntil=i+o.blockMs,n.set(t,e,c),`Too many attempts (${t}). Blocked for ${Math.ceil(o.blockMs/1e3)}s.`):null}function m(n,t,e,r,o){n.size(t)>=r.maxKeys&&n.evictOldest(t,Math.floor(r.maxKeys*.1));let i=n.get(t,e);!i||o-i.windowStartedAt>r.windowMs?n.set(t,e,{count:1,windowStartedAt:o,blockedUntil:0}):(i.count++,n.set(t,e,i))}export{g as a,f as b,w as c,A as d,b as e};
@@ -0,0 +1 @@
1
+ var r="accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()",i=Object.freeze({"Referrer-Policy":"strict-origin-when-cross-origin","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cross-Origin-Opener-Policy":"same-origin","Cross-Origin-Resource-Policy":"same-origin","X-DNS-Prefetch-Control":"off","X-Permitted-Cross-Domain-Policies":"none","Origin-Agent-Cluster":"?1","Permissions-Policy":r}),n=Object.entries(i).map(([e,o])=>({key:e,value:o}));export{r as a,i as b,n as c};
@@ -0,0 +1 @@
1
+ import{b as R}from"./chunk-FXUI75TW.js";import{getFrameworkConfig as S}from"@spring-systems/core/config";import{NextResponse as g}from"next/server.js";function O(r){let s=r.trim();if(!s)return[];try{let t=new URL(s),n=[t.origin];return t.protocol==="https:"?n.push(`wss://${t.host}`):t.protocol==="http:"&&n.push(`ws://${t.host}`),n}catch{return[]}}var j=new Set(["javascript:","data:","blob:","vbscript:","filesystem:"]);function l(r){let s=r.trim();if(!s||/[\s;,\r\n]/.test(s))return null;if(/^[a-zA-Z][a-zA-Z0-9+.-]*:$/.test(s)){let t=s.toLowerCase();return j.has(t)?null:t}try{let t=new URL(s);return!["http:","https:","ws:","wss:"].includes(t.protocol)||t.username||t.password||t.pathname!=="/"||t.search||t.hash?null:`${t.protocol}//${t.host}`}catch{return null}}function D(r){let s=r.trim();if(!s||/[\s;,\r\n"]/.test(s))return null;try{let t=new URL(s);return!["http:","https:"].includes(t.protocol)||t.username||t.password?null:t.toString()}catch{return null}}function u(r){let s=r.split(",").map(t=>l(t)).filter(t=>!!t);return[...new Set(s)]}function M(r){let s=r.nextUrl.clone(),t=process.env.TARGET_ENV||"test",n=t==="prod"||process.env.NODE_ENV==="production",o=t==="prod",i=s.hostname==="localhost"||s.hostname==="127.0.0.1",c=crypto.randomUUID().replace(/-/g,""),f=process.env.CSP_DENY_STYLE_ATTR==="true",E=!o,_=u(process.env.CSP_CONNECT_HOSTS||""),C=O(process.env.API_URL||""),P=u(process.env.CSP_IMG_HOSTS||""),w=u(process.env.CSP_SCRIPT_HOSTS||""),x=u(process.env.CSP_FRAME_HOSTS||""),p=S().csp,T=p.thirdPartyConnectSources.map(e=>l(e)).filter(e=>!!e),H=p.thirdPartyImageSources.map(e=>l(e)).filter(e=>!!e),U=p.thirdPartyScriptSources.map(e=>l(e)).filter(e=>!!e),b=p.thirdPartyFrameSources.map(e=>l(e)).filter(e=>!!e),L=["'self'",...T,...C,..._,...n?[]:["http://localhost:*","http://127.0.0.1:*","ws://localhost:*","ws://127.0.0.1:*"]].join(" "),$=["'self'","data:","blob:",...H,...P].join(" "),y=[...U,...w].join(" "),N=[...b,...x].join(" "),a=D(process.env.CSP_REPORT_URI||""),d=["default-src 'self'","base-uri 'self'","object-src 'none'",`script-src 'self' 'nonce-${c}' 'strict-dynamic' ${y}`,`script-src-elem 'self' 'nonce-${c}' 'strict-dynamic' ${y}`,"script-src-attr 'none'",E?"style-src 'self' 'unsafe-inline'":`style-src 'self' 'nonce-${c}'`,f?"style-src-attr 'none'":"style-src-attr 'unsafe-inline'",`img-src ${$}`,"media-src 'self' blob: data:","manifest-src 'self'",`connect-src ${L}`,`frame-src ${N||"'none'"}`,"frame-ancestors 'none'","form-action 'self'",...o?["upgrade-insecure-requests"]:[],...a?[`report-uri ${a}`,"report-to csp-violations"]:[]].join("; "),A=S().proxy.blockedPathPrefixesInProd??[];if(n){for(let e of A)if(s.pathname.startsWith(e)){let m=new g("Not Found",{status:404});return h(m,{isLocalHost:i,isTargetProd:o,csp:d,nonce:c,reportUri:a})}}if(s.pathname==="/"){let e=new URL(s.toString());!i&&o&&(e.protocol="https:"),e.pathname=S().app.defaultRoute,e.search="";let m=k(e,308);return h(m,{isLocalHost:i,isTargetProd:o,csp:d,nonce:c,reportUri:a})}let v=new Headers(r.headers);v.set("x-nonce",c);let I=g.next({request:{headers:v}});return h(I,{isLocalHost:i,isTargetProd:o,csp:d,nonce:c,reportUri:a})}function h(r,s){let{isLocalHost:t,csp:n,nonce:o,reportUri:i}=s;n&&r.headers.set("Content-Security-Policy",n);for(let[c,f]of Object.entries(R))r.headers.set(c,f);return s.isTargetProd&&!t&&r.headers.set("Strict-Transport-Security","max-age=63072000; includeSubDomains; preload"),i&&r.headers.set("Reporting-Endpoints",`csp-violations="${i}"`),o&&r.headers.set("x-nonce",o),r}function k(r,s=308){let t=new g(null,{status:s});return t.headers.set("Location",typeof r=="string"?r:r.toString()),t}var Y={matcher:["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|api/health).*)"]};export{M as a,Y as b};
@@ -0,0 +1 @@
1
+ import{b as g,c as T,d as h,e as E}from"./chunk-E4AX7MID.js";import{getFrameworkConfig as x,getSecureSessionCookieName as I,getSessionCookieName as H}from"@spring-systems/core/config";var U=604800;function M(){return Number(process.env.AUTH_COOKIE_MAX_AGE_SECONDS||"28800")}function F(){return(process.env.TARGET_ENV||"")==="prod"}function c(){return H(x().auth.sessionCookiePrefix)}function l(){return I(x().auth.sessionCookiePrefix)}function v(){return new Set(x().auth.sessionExpiredCodes)}function K(e){return e.nextUrl.hostname==="localhost"||e.nextUrl.hostname==="127.0.0.1"||e.nextUrl.hostname==="::1"||e.nextUrl.hostname==="[::1]"}function A(e){return K(e)?!1:F()?!0:e.nextUrl.protocol==="https:"}function D(e){return A(e)?l():c()}function S(e,t,r,n,o){let i=A(t);e.cookies.set({name:r,value:n,httpOnly:!0,secure:i,sameSite:"lax",path:"/",maxAge:o,priority:"high"})}function ne(e,t,r){let n=M(),o=Number.isFinite(n)&&n>0?Math.floor(n):28800,i=Math.min(o,U),s=D(t),u=s===c()?l():c();S(e,t,s,r,i),S(e,t,u,"",0)}function w(e,t,r){let n=A(t);e.cookies.set({name:r,value:"",httpOnly:!0,secure:n,sameSite:"lax",path:"/",maxAge:0,priority:"high"});let o=!n,i=[`${r}=`,"Path=/","Max-Age=0","HttpOnly","SameSite=Lax","Priority=High"];o&&i.push("Secure"),e.headers.append("Set-Cookie",i.join("; "))}function oe(e,t){w(e,t,c()),w(e,t,l())}function ie(e){let t=[c(),l()];for(let o of t){let i=(e.cookies.get(o)?.value||"").trim();if(i)return i}let r=e.headers.get("cookie")||"";if(!r)return"";let n=r.split(";").map(o=>o.trim());for(let o of n){if(!o)continue;let i=o.indexOf("=");if(i<=0)continue;let s=o.slice(0,i).trim();if(s!==c()&&s!==l())continue;let u=o.slice(i+1).trim();if(u)try{return decodeURIComponent(u)}catch{return u}}return""}function z(e){return typeof e=="string"?e.trim().toLowerCase():""}function L(e){let t=z(e);return t!==""&&v().has(t)}async function se(e){if(e.status!==403)return!1;try{let t=e.clone();try{let r=await t.json();return L(r.code)||L(r.error_code)}catch{return!1}}catch{return!1}}import{getFrameworkConfig as V}from"@spring-systems/core/config";function m(e){let t=e;if(typeof t.ip=="string")return t.ip.trim()||void 0}var G=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","proxy-connection"]);function a(e,t){return Number.isFinite(e)&&e>0?Math.floor(e):t}function ce(e){if(!e)return null;let t=Number(e);return!Number.isFinite(t)||t<0?null:Math.floor(t)}function ae(e){return e.replace(/^\/+|\/+$/g,"").toLowerCase()}function le(e){return!(!e||e==="."||e===".."||e.includes("\\")||e.includes("/")||/[\u0000-\u001F\u007F]/.test(e))}function fe(e){return e.match(/\/(v\d+(?:\.\d+)?)\/?$/i)?.[1]??null}function pe(e){return e.match(/^(v\d+(?:\.\d+)?)(?:\/|$)/i)?.[1]??null}function me(e){let t=(e.get("connection")||"").trim();return t?new Set(t.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean)):new Set}function de(e,t){let r=e.toLowerCase();return G.has(r)||t.has(r)}var p=class extends Error{code="PAYLOAD_TOO_LARGE";constructor(t){super(t),this.name="PayloadTooLargeError"}};function ge(e,t){let r=0;return e.pipeThrough(new TransformStream({transform(n,o){if(r+=n.byteLength,r>t){o.error(new p("Payload too large"));return}o.enqueue(n)}}))}function xe(e){let t=[e],r=new Set;for(;t.length>0;){let n=t.shift();if(!(!n||r.has(n))){if(r.add(n),n instanceof p)return!0;if(n instanceof Error){if(n.name==="PayloadTooLargeError"||n.code==="PAYLOAD_TOO_LARGE")return!0;let i=n.cause;i&&t.push(i)}else if(typeof n=="object"){let o=n;if(o.code==="PAYLOAD_TOO_LARGE"||typeof o.name=="string"&&o.name==="PayloadTooLargeError")return!0;o.cause&&t.push(o.cause)}}}return!1}var B=new Set(["POST","PUT","PATCH","DELETE"]);function q(){return(process.env.FRONTEND_URL||"").trim()}function _(){return(process.env.FRONTEND_IP||"").trim()}function X(){return process.env.AUTH_TRUST_PROXY_HEADERS==="true"}function O(){return new Set((process.env.CSRF_EXEMPT_PATHS||"").split(",").map(e=>e.trim().replace(/^\/+|\/+$/g,"").toLowerCase()).filter(Boolean))}function P(){return(process.env.NODE_ENV||"")==="production"||(process.env.TARGET_ENV||"")==="prod"}function d(e){try{return new URL(e).origin}catch{return null}}function Ce(e){let t=_();if(!t)return!1;let r=m(e);if(r)return r===t;if(!X())return!1;let o=(e.headers.get("x-forwarded-for")||"").trim().split(",")[0]?.trim();if(o)return o===t;let i=(e.headers.get("x-real-ip")||"").trim();return i?i===t:!1}function y(e){let t=(e.headers.get("origin")||"").trim();if(!t)return null;let r=d(t);if(!r)return null;let n=q(),o=n?d(n):null;if(o&&r===o)return r;let i=_();if(!i)return null;try{let s=new URL(r);return s.hostname!==i||!["http:","https:"].includes(s.protocol)||P()&&s.protocol!=="https:"||s.protocol==="http:"&&s.port!==""&&s.port!=="80"||s.protocol==="https:"&&s.port!==""&&s.port!=="443"?null:r}catch{return null}}function $(e){return B.has(e.toUpperCase())}function N(e,t){let r=(e.get("Vary")||"").trim();if(!r){e.set("Vary",t);return}let n=r.split(",").map(i=>i.trim()).filter(Boolean);if(n.some(i=>i==="*")){e.set("Vary","*");return}if(n.some(i=>i.toLowerCase()===t.toLowerCase())){e.set("Vary",n.join(", "));return}e.set("Vary",[...n,t].join(", "))}function j(e){let t=(e.headers.get("sec-fetch-site")||"").trim().toLowerCase();return t?!(t==="same-origin"||t==="same-site"||t==="none"):!1}function Y(e){let t=(e.headers.get("origin")||"").trim();if(!t)return!1;let r=d(t);if(!r)return!1;let n=e.nextUrl.origin;if(r===n)return!0;let o=y(e);return!!o&&o===r}function W(e){let t=(e.headers.get("referer")||"").trim();return t?d(t):null}function J(e){let t=W(e);if(!t)return!1;if(t===e.nextUrl.origin)return!0;let r=y(e);return!!r&&r===t}function Te(e,t,r){let n=O();return!$(t)||n.has(r.toLowerCase())?!1:j(e)?!0:!(Y(e)||J(e))}function he(e,t,r){let n=y(t),o=V().proxy.corsAllowedHeaders;if(r){n&&e.set("Access-Control-Allow-Origin",n),e.set("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, PATCH, OPTIONS"),e.set("Access-Control-Allow-Headers",o),e.set("Access-Control-Max-Age","86400"),n?e.set("Access-Control-Allow-Credentials","true"):e.delete("Access-Control-Allow-Credentials"),N(e,"Origin");return}n?(e.set("Access-Control-Allow-Origin",n),e.set("Access-Control-Allow-Credentials","true")):e.delete("Access-Control-Allow-Credentials"),N(e,"Origin"),e.set("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, PATCH, OPTIONS"),e.set("Access-Control-Allow-Headers",o),e.set("Access-Control-Max-Age","86400")}function Q(){return O().size>0}function Ee(){return P()&&Q()?"CSRF_EXEMPT_PATHS is not allowed in production":null}function R(){return process.env.AUTH_LOGIN_RATE_LIMIT_ENABLED!=="false"}function Z(){return process.env.AUTH_TRUST_PROXY_HEADERS==="true"}function f(e,t){return(e.headers.get(t)||"").trim()}function ee(e){let t=f(e,"user-agent").toLowerCase().slice(0,64),r=f(e,"accept-language").toLowerCase().slice(0,32),n=f(e,"sec-ch-ua").toLowerCase().slice(0,64),o=[t,r,n].filter(Boolean).join("|");return o?`fp:${o}`.slice(0,128):"unknown"}function C(){return{windowMs:a(Number(process.env.AUTH_LOGIN_RATE_LIMIT_WINDOW_MS||"900000"),9e5),blockMs:a(Number(process.env.AUTH_LOGIN_RATE_LIMIT_BLOCK_MS||"900000"),9e5),maxAttemptsByIpAndAccount:a(Number(process.env.AUTH_LOGIN_RATE_LIMIT_MAX_ATTEMPTS||"8"),8),maxAttemptsByAccount:a(Number(process.env.AUTH_LOGIN_RATE_LIMIT_ACCOUNT_MAX_ATTEMPTS||"20"),20),maxKeys:a(Number(process.env.AUTH_LOGIN_RATE_LIMIT_MAX_KEYS||"10000"),1e4)}}function te(e){if(Z()){let n=f(e,"x-forwarded-for").split(",")[0]?.trim();if(n)return n;let o=f(e,"x-real-ip");if(o)return o}let t=m(e);return t||ee(e)}function b(e){let t=e.trim().toLowerCase();return t?t.slice(0,128):"unknown"}function Ne(e,t){let r=b(t);return{pairKey:`${b(te(e))}:${r}`,accountKey:r!=="unknown"?r:null}}function _e(e){let t=C(),r=g();for(let n of["ip","account"]){typeof r.sweepExpired=="function"&&r.sweepExpired(n,e,t.windowMs);let o=r.size(n);o>t.maxKeys&&r.evictOldest(n,o-t.maxKeys)}}function Oe(e,t){if(!R())return 0;let r=C();if(!T(e.pairKey,e.accountKey,r))return 0;let o=g(),i=o.get("ip",e.pairKey),s=e.accountKey?o.get("account",e.accountKey):null,u=i&&i.blockedUntil>t?i.blockedUntil-t:0,k=s&&s.blockedUntil>t?s.blockedUntil-t:0;return Math.max(u,k)}function Pe(e,t){if(!R())return;let r=C();h(e.pairKey,e.accountKey,r)}function be(e){R()&&E(e.pairKey,e.accountKey)}export{a,ce as b,ae as c,le as d,fe as e,pe as f,me as g,de as h,ge as i,xe as j,K as k,A as l,D as m,ne as n,oe as o,ie as p,L as q,se as r,d as s,Ce as t,y as u,Te as v,he as w,Q as x,Ee as y,te as z,Ne as A,_e as B,Oe as C,Pe as D,be as E};
@@ -0,0 +1 @@
1
+ import{getRuntimeEnvKeys as i}from"@spring-systems/core/config";import{NextResponse as s}from"next/server.js";async function p(){let e=(process.env.NODE_ENV||"")==="production"||(process.env.TARGET_ENV||"")==="prod",n=(process.env.RUNTIME_ENV_PUBLIC_IN_PROD||"").toLowerCase()==="true";if(e&&!n)return s.json({error:"Not found"},{status:404});let t=Object.fromEntries(i().map(r=>[r,process.env[r]]).filter(([,r])=>typeof r=="string"));return s.json(t,{status:200,headers:{"Cache-Control":"no-store, no-cache, must-revalidate, proxy-revalidate",Pragma:"no-cache",Expires:"0"}})}import{getRuntimeEnvKeys as c}from"@spring-systems/core/config";import{jsx as m}from"react/jsx-runtime";function u(e){return JSON.stringify(e).replace(/</g,"\\u003C").replace(/>/g,"\\u003E").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}function a({nonce:e}){let n={};for(let r of c()){let o=process.env[r];typeof o=="string"&&(n[r]=o)}let t=`window.__RUNTIME_ENV__ = ${u(n)};`;return m("script",{id:"runtime-env",nonce:e,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:t}})}export{p as a,a as b};
@@ -0,0 +1 @@
1
+ import o from"next/dynamic";import p from"next/image";import u from"next/link";import{useParams as m,usePathname as i,useRouter as c,useSearchParams as d}from"next/navigation";var t=!1;function N(){return{usePathname:i,useSearchParams:()=>d()??new URLSearchParams,useParams:()=>m()??{},useRouter:c}}function r(e){let a=e?.defaultDynamicSsr??!0;return e?.defaultDynamicSsr===void 0&&!t&&process.env.NODE_ENV!=="production"&&(t=!0,console.warn("[server] createNextUIAdapter() defaults dynamic imports to ssr:true. For client-only apps use createClientOnlyNextUIAdapter() or pass { defaultDynamicSsr: false }.")),{Image:p,Link:u,dynamic:(s,n)=>o(s,{ssr:n?.ssr??a})}}function P(){return r({defaultDynamicSsr:!1})}function I(){return r({defaultDynamicSsr:!0})}export{N as a,r as b,P as c,I as d};
@@ -0,0 +1 @@
1
+ import{A as J,B as fe,C as ge,D as me,E as ye,a as Y,b as ne,c as V,d as oe,e as se,f as ae,g as G,h as K,i as re,j as ie,k as ce,n as le,o as v,p as he,r as ue,t as P,v as pe,w as R,y as de}from"./chunk-M23YQYLU.js";import{getFrameworkConfig as L}from"@spring-systems/core/config";import{logInfo as Ce,logWarn as Pe}from"@spring-systems/core/logger";import{NextResponse as C}from"next/server.js";var W;function Ee(){if(W!==void 0)return W;let e=(process.env.API_URL||"").trim();if(!e)throw new Error("API_URL environment variable is not set");try{new URL(e)}catch{throw new Error(`API_URL is not a valid URL: "${e}"`)}return W=e,e}function Re(e,n){let t=process.env[e];if(!t)return n;let l=Number(t);if(!Number.isFinite(l)||l<=0)throw new Error(`${e} must be a positive number, got: "${t}"`);return l}var Ue=Re("API_PROXY_NON_MULTIPART_MAX_BYTES",10485760),ke=Re("API_PROXY_MULTIPART_MAX_BYTES",52428800),be=new Set([400,401,403,404,429,502]),Be=process.env.AUTH_DEBUG==="true";function He(){return new Set(L().auth.publicAuthPaths)}function ve(){return L().proxy.safeHashPattern}function Ie(){return L().proxy.maxProxyPathLength}function Ne(){return de()}function Oe(e){return e?Y(ke,52428800):Y(Ue,10485760)}function I(e,n){Be&&Ce(`AuthProxy.${e}`,n)}function $e(e){let n=e.trim();if(!n)return"";let t=ve();return!t||!t.test(n)?"":n}function u(e,n,t,l){let h=new Headers(l);return R(h,e,P(e)),C.json({error:n},{status:t,headers:h})}async function je(e,n){let{path:t}=await n.params;return E(e,t,"GET")}async function Ye(e,n){let{path:t}=await n.params;return E(e,t,"POST")}async function Ve(e,n){let{path:t}=await n.params;return E(e,t,"PUT")}async function Ge(e,n){let{path:t}=await n.params;return E(e,t,"DELETE")}async function Ke(e,n){let{path:t}=await n.params;return E(e,t,"PATCH")}async function E(e,n,t){let l=n.join("/");try{fe(Date.now());let h=Ne();if(h)return u(e,h,500);if(!n.every(o=>oe(o)))return u(e,"Invalid path",400);if(l.length>Ie())return u(e,"Path too long",414);let w=Ee().replace(/\/+$/,"");if(!w)return u(e,"API_URL is not configured",500);let U=new URL(w).pathname,k=se(U),N=ae(l),O=l;k&&N&&k.toLowerCase()===N.toLowerCase()&&(O=l.replace(new RegExp(`^${N}/?`,"i"),""));let Le=`${w}/${O.replace(/^\/+/,"")}`,r=V(O),T=L().auth,Q=(T.loginPath??"auth/login").toLowerCase(),Z=(T.logoutPath??"auth/logout").toLowerCase(),b=(T.infoPath??"auth/info").toLowerCase(),$=r===Q,D=r===Z,we=r===Q||r===Z||r===b,Te=new URL(e.url),g=new URL(Le);Te.searchParams.forEach((o,c)=>{g.searchParams.has(c)||g.searchParams.set(c,o)});let p=new Headers,xe=G(e.headers);e.headers.forEach((o,c)=>{let a=c.toLowerCase();a==="host"||a==="content-length"||a==="cookie"||a==="authorization"||K(a,xe)||p.set(c,o)}),p.has("X-Requested-With")||p.set("X-Requested-With","XMLHttpRequest");let x=he(e),q=(e.headers.get("authorization")||"").trim(),M=/^Bearer\s+\S+$/i.test(q);if(I("incoming-auth",{path:r,method:t,hasSessionToken:!!x,hasBearerAuthorization:M,host:e.nextUrl.host,protocol:e.nextUrl.protocol}),r===b&&!x&&!M){let o=new C(null,{status:204});return R(o.headers,e,P(e)),o.headers.set("Cache-Control","no-store, no-cache, must-revalidate, proxy-revalidate"),o.headers.set("Pragma","no-cache"),o.headers.set("Expires","0"),v(o,e),I("short-circuit-auth-info-no-token",{host:e.nextUrl.host,protocol:e.nextUrl.protocol}),o}if(pe(e,t,r))return Pe("ApiProxy.CSRF",`Blocked ${t} ${r} \u2014 origin: ${e.headers.get("origin")??"none"}, referer: ${e.headers.get("referer")??"none"}, sec-fetch-site: ${e.headers.get("sec-fetch-site")??"none"}`),u(e,"Forbidden",403);x&&!He().has(r)?p.set("Authorization",`Bearer ${x}`):M&&p.set("Authorization",q);let Ae=e.headers.get("x-hash")||"",A=$e(Ae);A&&(p.set("X-hash",A),p.set("Hash",A),g.searchParams.has("hash")||g.searchParams.set("hash",A),g.searchParams.has("Hash")||g.searchParams.set("Hash",A));let y,d=null;if(t!=="GET"&&t!=="HEAD"){let o=e.headers.get("content-type")||"",c=o.includes("multipart/form-data"),a=Oe(c),m=ne(e.headers.get("content-length"));if(m!==null&&m>a)return u(e,"Payload too large",413);if(c){if(m===null)return u(e,"Content-Length required for multipart payload",411);y=e.body?re(e.body,a):e.body}else{if(y=await e.arrayBuffer(),y.byteLength>a)return u(e,"Payload too large",413);if($&&o.includes("application/json"))try{let B=new TextDecoder().decode(y),H=JSON.parse(B),S=T.loginUsernameField??"username";d=J(e,String(H[S]||""));let j=ge(d,Date.now());if(j>0)return Pe("ApiProxy.RateLimit",`Login rate-limited for key ${d.pairKey} \u2014 retry after ${Math.ceil(j/1e3)}s`),u(e,"Too many login attempts. Try again later.",429,{"Retry-After":String(Math.ceil(j/1e3))})}catch{d=J(e,"")}}}let ee={method:t,headers:p,body:y,redirect:"manual",signal:AbortSignal.timeout(L().api?.timeoutMs??3e4)};y instanceof ReadableStream&&(ee.duplex="half");let s=await fetch(g.toString(),ee),i=new Headers,_e=G(s.headers);s.headers.forEach((o,c)=>{let a=c.toLowerCase();a==="content-encoding"||a==="content-length"||K(a,_e)||i.set(c,o)});let _=null,z="",F=s.status;if($&&(s.headers.get("content-type")||"").toLowerCase().includes("application/json"))try{let c=await s.text(),a=JSON.parse(c),m=T.tokenResponseFields??{accessToken:"access_token",refreshToken:"refresh_token"},B=a[m.accessToken],H=typeof B=="string"?B.trim():"";H&&(z=H);let S={...a};delete S[m.accessToken],delete S[m.refreshToken],_=JSON.stringify(S),i.set("content-type","application/json; charset=utf-8"),i.delete("content-length")}catch{_=JSON.stringify({error:"Invalid response from authentication service"}),F=502,i.set("content-type","application/json; charset=utf-8"),i.delete("content-length")}let Se=P(e);R(i,e,Se),we?(i.set("Cache-Control","no-store, no-cache, must-revalidate, proxy-revalidate"),i.set("Pragma","no-cache"),i.set("Expires","0")):i.has("Cache-Control")||i.set("Cache-Control","private, no-store"),D&&s.status>=200&&s.status<300&&!ce(e)&&i.set("Clear-Site-Data",'"cache", "storage"');let te=await ue(s);_||(_=s.body);let X=new C(_,{status:F,headers:i});return z&&s.status>=200&&s.status<300?(le(X,e,z),d&&ye(d)):$&&d&&be.has(F)?me(d,Date.now()):(D||s.status===401||r===b&&s.status===403||te)&&(I("clear-session-cookie",{path:r,method:t,status:s.status,reason:D?"logout":s.status===401?"status_401":r===b&&s.status===403?"auth_info_403":te?"forbidden_session_text":"unknown",hadSessionToken:!!x}),v(X,e)),X}catch(h){if(ie(h))return u(e,"Payload too large",413);let w=process.env.NODE_ENV==="development",f=C.json({error:"Proxy request failed",...w?{details:h instanceof Error?h.message:String(h)}:{}},{status:500}),U=V(l).replace(/^v\d+(?:\.\d+)?\/?/i,""),k=(L().auth.logoutPath??"auth/logout").toLowerCase();return R(f.headers,e,P(e)),U===k&&(v(f,e),f.headers.set("Cache-Control","no-store, no-cache, must-revalidate, proxy-revalidate"),f.headers.set("Pragma","no-cache"),f.headers.set("Expires","0"),I("clear-session-cookie",{path:U,method:t,status:500,reason:"logout_proxy_failure"})),f}}async function Je(e){let n=new Headers,t=P(e);return R(n,e,t),new C(null,{status:204,headers:n})}export{je as a,Ye as b,Ve as c,Ge as d,Ke as e,Je as f};
package/dist/client.js CHANGED
@@ -1,14 +1 @@
1
- "use client";
2
- import {
3
- createClientOnlyNextUIAdapter,
4
- createNextRouteAdapter
5
- } from "./chunk-CP33WQ5Q.js";
6
-
7
- // src/client.ts
8
- var SPRING_SERVER_VERSION = true ? "0.8.4" : "0.0.0-dev";
9
- export {
10
- SPRING_SERVER_VERSION,
11
- createClientOnlyNextUIAdapter,
12
- createNextRouteAdapter
13
- };
14
- //# sourceMappingURL=client.js.map
1
+ "use client";import{a as e,c as t}from"./chunk-UDRRRHFI.js";var _="0.8.7";export{_ as SPRING_SERVER_VERSION,t as createClientOnlyNextUIAdapter,e as createNextRouteAdapter};
@@ -1,48 +1 @@
1
- import {
2
- applyCorsHeaders,
3
- cleanupExpiredLoginLimits,
4
- clearLoginAttemptState,
5
- clearSessionCookie,
6
- getClientAddress,
7
- getLoginRateLimitKeys,
8
- getRateLimitRetryAfterMs,
9
- getSessionToken,
10
- hasCsrfExemptPaths,
11
- isInternalIpAccess,
12
- isLocalHostRequest,
13
- isSessionExpiredCode,
14
- normalizeOrigin,
15
- registerFailedLoginAttempt,
16
- resolveAllowedOrigin,
17
- resolveProdSecurityConfigError,
18
- resolveSessionCookieName,
19
- setSessionCookie,
20
- shouldClearSessionFromForbidden,
21
- shouldRejectByCsrfProtection,
22
- useSecureCookies
23
- } from "../chunk-PZWKMIA4.js";
24
- import "../chunk-7IUSTA5W.js";
25
- export {
26
- applyCorsHeaders,
27
- cleanupExpiredLoginLimits,
28
- clearLoginAttemptState,
29
- clearSessionCookie,
30
- getClientAddress,
31
- getLoginRateLimitKeys,
32
- getRateLimitRetryAfterMs,
33
- getSessionToken,
34
- hasCsrfExemptPaths,
35
- isInternalIpAccess,
36
- isLocalHostRequest,
37
- isSessionExpiredCode,
38
- normalizeOrigin,
39
- registerFailedLoginAttempt,
40
- resolveAllowedOrigin,
41
- resolveProdSecurityConfigError,
42
- resolveSessionCookieName,
43
- setSessionCookie,
44
- shouldClearSessionFromForbidden,
45
- shouldRejectByCsrfProtection,
46
- useSecureCookies
47
- };
48
- //# sourceMappingURL=index.js.map
1
+ import{A as q,B as r,C as s,D as t,E as u,k as a,l as b,m as c,n as d,o as e,p as f,q as g,r as h,s as i,t as j,u as k,v as l,w as m,x as n,y as o,z as p}from"../chunk-M23YQYLU.js";import"../chunk-E4AX7MID.js";export{m as applyCorsHeaders,r as cleanupExpiredLoginLimits,u as clearLoginAttemptState,e as clearSessionCookie,p as getClientAddress,q as getLoginRateLimitKeys,s as getRateLimitRetryAfterMs,f as getSessionToken,n as hasCsrfExemptPaths,j as isInternalIpAccess,a as isLocalHostRequest,g as isSessionExpiredCode,i as normalizeOrigin,t as registerFailedLoginAttempt,k as resolveAllowedOrigin,o as resolveProdSecurityConfigError,c as resolveSessionCookieName,d as setSessionCookie,h as shouldClearSessionFromForbidden,l as shouldRejectByCsrfProtection,b as useSecureCookies};
package/dist/index.js CHANGED
@@ -1,44 +1 @@
1
- import {
2
- proxy,
3
- proxyConfig
4
- } from "./chunk-4SUIIQDW.js";
5
- import {
6
- DELETE,
7
- GET,
8
- OPTIONS,
9
- PATCH,
10
- POST,
11
- PUT
12
- } from "./chunk-NFJ25NQQ.js";
13
- import "./chunk-PZWKMIA4.js";
14
- import {
15
- GET as GET2,
16
- RuntimeEnvScript
17
- } from "./chunk-YV6DZVPI.js";
18
- import "./chunk-7IUSTA5W.js";
19
- import {
20
- BASE_SECURITY_HEADERS,
21
- BASE_SECURITY_HEADER_VALUES,
22
- PERMISSIONS_POLICY_VALUE
23
- } from "./chunk-KA7RJCWA.js";
24
-
25
- // src/index.ts
26
- import "server-only";
27
- var SPRING_SERVER_VERSION = true ? "0.8.4" : "0.0.0-dev";
28
- export {
29
- BASE_SECURITY_HEADERS,
30
- BASE_SECURITY_HEADER_VALUES,
31
- DELETE,
32
- GET,
33
- OPTIONS,
34
- PATCH,
35
- PERMISSIONS_POLICY_VALUE,
36
- POST,
37
- PUT,
38
- RuntimeEnvScript,
39
- SPRING_SERVER_VERSION,
40
- proxy,
41
- proxyConfig,
42
- GET2 as runtimeEnvGET
43
- };
44
- //# sourceMappingURL=index.js.map
1
+ import{a as I,b as p}from"./chunk-KEEIJ5EV.js";import{a as E,b as _,c as S,d as r,e as o,f as t}from"./chunk-VB7DEUGT.js";import"./chunk-M23YQYLU.js";import{a as O,b as T}from"./chunk-TVAJDSYB.js";import"./chunk-E4AX7MID.js";import{a as e,b as R,c as n}from"./chunk-FXUI75TW.js";import"server-only";var f="0.8.7";export{n as BASE_SECURITY_HEADERS,R as BASE_SECURITY_HEADER_VALUES,r as DELETE,E as GET,t as OPTIONS,o as PATCH,e as PERMISSIONS_POLICY_VALUE,_ as POST,S as PUT,T as RuntimeEnvScript,f as SPRING_SERVER_VERSION,I as proxy,p as proxyConfig,O as runtimeEnvGET};
@@ -1,14 +1 @@
1
- "use client";
2
- import {
3
- createClientOnlyNextUIAdapter,
4
- createNextRouteAdapter,
5
- createNextUIAdapter,
6
- createSsrNextUIAdapter
7
- } from "./chunk-CP33WQ5Q.js";
8
- export {
9
- createClientOnlyNextUIAdapter,
10
- createNextRouteAdapter,
11
- createNextUIAdapter,
12
- createSsrNextUIAdapter
13
- };
14
- //# sourceMappingURL=next-adapters.js.map
1
+ "use client";import{a,b,c,d}from"./chunk-UDRRRHFI.js";export{c as createClientOnlyNextUIAdapter,a as createNextRouteAdapter,b as createNextUIAdapter,d as createSsrNextUIAdapter};
@@ -1,10 +1 @@
1
- import {
2
- proxy,
3
- proxyConfig
4
- } from "./chunk-4SUIIQDW.js";
5
- import "./chunk-KA7RJCWA.js";
6
- export {
7
- proxy,
8
- proxyConfig
9
- };
10
- //# sourceMappingURL=proxy-middleware.js.map
1
+ import{a,b}from"./chunk-KEEIJ5EV.js";import"./chunk-FXUI75TW.js";export{a as proxy,b as proxyConfig};
@@ -1,15 +1 @@
1
- import {
2
- checkRateLimit,
3
- clearRateLimitEntries,
4
- getRateLimiterAdapter,
5
- recordFailedAttempt,
6
- setRateLimiterAdapter
7
- } from "./chunk-7IUSTA5W.js";
8
- export {
9
- checkRateLimit,
10
- clearRateLimitEntries,
11
- getRateLimiterAdapter,
12
- recordFailedAttempt,
13
- setRateLimiterAdapter
14
- };
15
- //# sourceMappingURL=rate-limiter.js.map
1
+ import{a,b,c,d,e}from"./chunk-E4AX7MID.js";export{c as checkRateLimit,e as clearRateLimitEntries,b as getRateLimiterAdapter,d as recordFailedAttempt,a as setRateLimiterAdapter};
@@ -1,9 +1 @@
1
- import {
2
- GET,
3
- RuntimeEnvScript
4
- } from "./chunk-YV6DZVPI.js";
5
- export {
6
- RuntimeEnvScript,
7
- GET as runtimeEnvGET
8
- };
9
- //# sourceMappingURL=runtime-env.js.map
1
+ import{a,b}from"./chunk-TVAJDSYB.js";export{b as RuntimeEnvScript,a as runtimeEnvGET};
@@ -1,11 +1 @@
1
- import {
2
- BASE_SECURITY_HEADERS,
3
- BASE_SECURITY_HEADER_VALUES,
4
- PERMISSIONS_POLICY_VALUE
5
- } from "./chunk-KA7RJCWA.js";
6
- export {
7
- BASE_SECURITY_HEADERS,
8
- BASE_SECURITY_HEADER_VALUES,
9
- PERMISSIONS_POLICY_VALUE
10
- };
11
- //# sourceMappingURL=security-headers.js.map
1
+ import{a,b,c}from"./chunk-FXUI75TW.js";export{c as BASE_SECURITY_HEADERS,b as BASE_SECURITY_HEADER_VALUES,a as PERMISSIONS_POLICY_VALUE};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spring-systems/server",
3
- "version": "0.8.4",
3
+ "version": "0.8.7",
4
4
  "description": "Next.js server-only code for the Spring Systems framework (proxy, API routes, runtime env)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -97,8 +97,8 @@
97
97
  "lint": "eslint src/"
98
98
  },
99
99
  "peerDependencies": {
100
- "@spring-systems/core": "^0.8.4",
101
- "@spring-systems/ui": "^0.8.4",
100
+ "@spring-systems/core": "^0.8.6",
101
+ "@spring-systems/ui": "^0.8.6",
102
102
  "next": "^16.0.0",
103
103
  "react": "^19.0.0"
104
104
  },
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -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