@luckystack/server 0.4.0 → 0.4.1
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/dist/index.d.ts +6 -1
- package/dist/index.js +30 -12
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.d.ts
CHANGED
|
@@ -281,7 +281,12 @@ declare const getPreParamsCustomRoutes: () => readonly CustomRouteHandler[];
|
|
|
281
281
|
declare const clearCustomRoutes: () => void;
|
|
282
282
|
|
|
283
283
|
interface OriginExemptMatcher {
|
|
284
|
-
/**
|
|
284
|
+
/**
|
|
285
|
+
* A route is exempt when its path equals this prefix OR continues past it on a
|
|
286
|
+
* path-SEGMENT boundary (i.e. `<prefix>/...`). Matching is boundary-aware so a
|
|
287
|
+
* prefix can't bleed into a sibling route — `/webhooks` exempts `/webhooks` and
|
|
288
|
+
* `/webhooks/stripe` but NOT `/webhooksadmin`.
|
|
289
|
+
*/
|
|
285
290
|
pathPrefix: string;
|
|
286
291
|
}
|
|
287
292
|
declare const registerOriginExemptPath: (matcher: OriginExemptMatcher) => void;
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/createServer.ts
|
|
9
9
|
import http from "http";
|
|
10
|
-
import { registerBindAddress, writeBootUuid, getLogger as getLogger13, getProjectConfig as getProjectConfig13, tryCatch as tryCatch10, isProduction as isProduction2, resolveEnvKey as
|
|
10
|
+
import { registerBindAddress, writeBootUuid, getLogger as getLogger13, getProjectConfig as getProjectConfig13, tryCatch as tryCatch10, isProduction as isProduction2, resolveEnvKey as resolveEnvKey5 } from "@luckystack/core";
|
|
11
11
|
|
|
12
12
|
// src/httpHandler.ts
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
@@ -138,7 +138,12 @@ var getOriginExemptPaths = () => exemptPaths;
|
|
|
138
138
|
var clearOriginExemptPaths = () => {
|
|
139
139
|
exemptPaths.length = 0;
|
|
140
140
|
};
|
|
141
|
-
var isOriginExemptPath = (routePath) => exemptPaths.some((
|
|
141
|
+
var isOriginExemptPath = (routePath) => exemptPaths.some(({ pathPrefix }) => {
|
|
142
|
+
if (!pathPrefix) return false;
|
|
143
|
+
if (routePath === pathPrefix) return true;
|
|
144
|
+
const boundary = pathPrefix.endsWith("/") ? pathPrefix : `${pathPrefix}/`;
|
|
145
|
+
return routePath.startsWith(boundary);
|
|
146
|
+
});
|
|
142
147
|
|
|
143
148
|
// src/httpRoutes/timingSafeEqual.ts
|
|
144
149
|
import { timingSafeEqual as cryptoTimingSafeEqual } from "crypto";
|
|
@@ -427,7 +432,12 @@ import {
|
|
|
427
432
|
} from "@luckystack/core";
|
|
428
433
|
|
|
429
434
|
// src/httpRoutes/sessionCookie.ts
|
|
430
|
-
|
|
435
|
+
import { resolveEnvKey as resolveEnvKey2 } from "@luckystack/core";
|
|
436
|
+
var resolveCookieSecure = (sessionCookieSecure, secureEnv) => {
|
|
437
|
+
if (sessionCookieSecure !== void 0) return sessionCookieSecure;
|
|
438
|
+
if (secureEnv === "true") return true;
|
|
439
|
+
return resolveEnvKey2() === "production";
|
|
440
|
+
};
|
|
431
441
|
|
|
432
442
|
// src/httpRoutes/authApiRoute.ts
|
|
433
443
|
var parseSessionBasedTokenHeader = (headerValue) => {
|
|
@@ -481,7 +491,7 @@ var handleAuthApiRoute = async ({
|
|
|
481
491
|
});
|
|
482
492
|
const oauthInitLimit = oauthRateLimiting.defaultApiLimit !== false && oauthRateLimiting.defaultApiLimit > 0 ? oauthRateLimiting.defaultApiLimit : oauthRateLimiting.auth.enabled && oauthRateLimiting.auth.maxAttempts > 0 ? oauthRateLimiting.auth.maxAttempts : null;
|
|
483
493
|
if (oauthInitLimit !== null) {
|
|
484
|
-
const oauthInitWindowMs = oauthRateLimiting.defaultApiLimit
|
|
494
|
+
const oauthInitWindowMs = oauthRateLimiting.defaultApiLimit !== false && oauthRateLimiting.defaultApiLimit > 0 ? oauthRateLimiting.windowMs : oauthRateLimiting.auth.windowMs;
|
|
485
495
|
const { allowed, resetIn } = await checkRateLimit({
|
|
486
496
|
key: `ip:${oauthRequesterIp}:auth:oauth-init`,
|
|
487
497
|
limit: oauthInitLimit,
|
|
@@ -551,7 +561,7 @@ var handleAuthApiRoute = async ({
|
|
|
551
561
|
});
|
|
552
562
|
const ipLimitCount = rateLimiting.defaultApiLimit !== false && rateLimiting.defaultApiLimit > 0 ? rateLimiting.defaultApiLimit : rateLimiting.auth.enabled && rateLimiting.auth.maxAttempts > 0 ? rateLimiting.auth.maxAttempts : null;
|
|
553
563
|
if (ipLimitCount !== null) {
|
|
554
|
-
const ipWindowMs = rateLimiting.defaultApiLimit
|
|
564
|
+
const ipWindowMs = rateLimiting.defaultApiLimit !== false && rateLimiting.defaultApiLimit > 0 ? rateLimiting.windowMs : rateLimiting.auth.windowMs;
|
|
555
565
|
const { allowed, resetIn } = await checkRateLimit({
|
|
556
566
|
key: `ip:${requesterIp}:auth:credentials`,
|
|
557
567
|
limit: ipLimitCount,
|
|
@@ -1197,7 +1207,7 @@ var enforceOriginPolicy = (req, res, routePath) => {
|
|
|
1197
1207
|
});
|
|
1198
1208
|
const isStateChangingMethod = req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS";
|
|
1199
1209
|
if (isOriginExemptPath(routePath)) {
|
|
1200
|
-
return { origin, rejected: false };
|
|
1210
|
+
return { origin: "", rejected: false };
|
|
1201
1211
|
}
|
|
1202
1212
|
if (!origin) {
|
|
1203
1213
|
if (isStateChangingMethod) {
|
|
@@ -1760,7 +1770,7 @@ import {
|
|
|
1760
1770
|
isLocalizedNormalizerRegistered,
|
|
1761
1771
|
isProjectConfigRegistered,
|
|
1762
1772
|
isRuntimeMapsProviderRegistered,
|
|
1763
|
-
resolveEnvKey as
|
|
1773
|
+
resolveEnvKey as resolveEnvKey3
|
|
1764
1774
|
} from "@luckystack/core";
|
|
1765
1775
|
var verifyBootstrap = async (requirements = {}) => {
|
|
1766
1776
|
const missing = [];
|
|
@@ -1799,7 +1809,7 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
1799
1809
|
}
|
|
1800
1810
|
}
|
|
1801
1811
|
if (!isRuntimeMapsProviderRegistered()) {
|
|
1802
|
-
if (
|
|
1812
|
+
if (resolveEnvKey3() === "production") {
|
|
1803
1813
|
missing.push(
|
|
1804
1814
|
"RuntimeMapsProvider \u2014 call `registerRuntimeMapsProvider({...})` from `server/prod/runtimeMaps.ts`. Without it, every api/sync request returns notFound."
|
|
1805
1815
|
);
|
|
@@ -1810,7 +1820,7 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
1810
1820
|
}
|
|
1811
1821
|
}
|
|
1812
1822
|
if (!isLocalizedNormalizerRegistered()) {
|
|
1813
|
-
if (
|
|
1823
|
+
if (resolveEnvKey3() === "production") {
|
|
1814
1824
|
missing.push(
|
|
1815
1825
|
"LocalizedNormalizer \u2014 call `registerLocalizedNormalizer({...})` from your bootstrap. Without it, error response messages will be the raw errorCode (no i18n)."
|
|
1816
1826
|
);
|
|
@@ -1829,6 +1839,14 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
1829
1839
|
);
|
|
1830
1840
|
}
|
|
1831
1841
|
}
|
|
1842
|
+
if (isProjectConfigRegistered()) {
|
|
1843
|
+
const cfg = getProjectConfig12();
|
|
1844
|
+
if (!cfg.session.basedToken && cfg.http.sessionCookieSameSite !== "Strict") {
|
|
1845
|
+
getLogger9().warn(
|
|
1846
|
+
`[LuckyStack] SECURITY: http.sessionCookieSameSite is '${cfg.http.sessionCookieSameSite}', not 'Strict', but the CSRF-exempt auth-bootstrap endpoints (/auth/api/credentials, /auth/callback/*) rely on SameSite=Strict to block cross-site login-CSRF. Use 'Strict', or add your own CSRF/origin check on those routes.`
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1832
1850
|
if (missing.length === 0) return;
|
|
1833
1851
|
const detail = missing.map((line, idx) => ` ${idx + 1}. ${line}`).join("\n");
|
|
1834
1852
|
throw new Error(
|
|
@@ -1845,7 +1863,7 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
1845
1863
|
import {
|
|
1846
1864
|
getLogger as getLogger10,
|
|
1847
1865
|
registerRuntimeMapsProvider,
|
|
1848
|
-
resolveEnvKey as
|
|
1866
|
+
resolveEnvKey as resolveEnvKey4
|
|
1849
1867
|
} from "@luckystack/core";
|
|
1850
1868
|
var emptyRuntimeMaps = {
|
|
1851
1869
|
apisObject: {},
|
|
@@ -1895,7 +1913,7 @@ var mergeInto = (target, source, kind, fromPreset, keyOrigin) => {
|
|
|
1895
1913
|
target[key] = source[key];
|
|
1896
1914
|
}
|
|
1897
1915
|
};
|
|
1898
|
-
var isProduction = () =>
|
|
1916
|
+
var isProduction = () => resolveEnvKey4() === "production";
|
|
1899
1917
|
var createProdRuntimeMapsProvider = (options) => {
|
|
1900
1918
|
let prodMapsPromise = null;
|
|
1901
1919
|
let devkitModulePromise = null;
|
|
@@ -2137,7 +2155,7 @@ var createLuckyStackServer = async (options = {}) => {
|
|
|
2137
2155
|
});
|
|
2138
2156
|
const port = options.port ?? getParsedPort() ?? options.defaultPort ?? process.env.SERVER_PORT ?? 80;
|
|
2139
2157
|
const ip = options.ip ?? process.env.SERVER_IP ?? "127.0.0.1";
|
|
2140
|
-
const enableDevTools = options.enableDevTools ??
|
|
2158
|
+
const enableDevTools = options.enableDevTools ?? resolveEnvKey5() !== "production";
|
|
2141
2159
|
registerBindAddress({
|
|
2142
2160
|
ip,
|
|
2143
2161
|
port: typeof port === "string" ? Number.parseInt(port, 10) : port
|