@rpcbase/server 0.461.0 → 0.463.0
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/applyRouteLoaders.d.ts +6 -1
- package/dist/applyRouteLoaders.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +321 -20
- package/dist/passwordHashStorage.d.ts +11 -0
- package/dist/passwordHashStorage.d.ts.map +1 -0
- package/dist/passwordHashStorage.test.d.ts +2 -0
- package/dist/passwordHashStorage.test.d.ts.map +1 -0
- package/dist/renderSSR.d.ts +4 -1
- package/dist/renderSSR.d.ts.map +1 -1
- package/dist/ssrMiddleware.d.ts.map +1 -1
- package/package.json +11 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Request } from 'express';
|
|
2
2
|
import { StaticHandlerContext } from '../../router/src';
|
|
3
|
-
export
|
|
3
|
+
export type RouterContextWithRedirect = StaticHandlerContext & {
|
|
4
|
+
redirectResponse?: Response;
|
|
5
|
+
redirectRouteId?: string | null;
|
|
6
|
+
redirectRoutePath?: string | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function applyRouteLoaders(req: Request, dataRoutes: any[]): Promise<RouterContextWithRedirect>;
|
|
4
9
|
//# sourceMappingURL=applyRouteLoaders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyRouteLoaders.d.ts","sourceRoot":"","sources":["../src/applyRouteLoaders.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAA;AAC/B,OAAO,EACL,oBAAoB,EAMrB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"applyRouteLoaders.d.ts","sourceRoot":"","sources":["../src/applyRouteLoaders.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAA;AAC/B,OAAO,EACL,oBAAoB,EAMrB,MAAM,iBAAiB,CAAA;AAuFxB,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,gBAAgB,CAAC,EAAE,QAAQ,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAA;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,GAAG,EAAE,GAChB,OAAO,CAAC,yBAAyB,CAAC,CA6KpC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createReadStream, readFileSync } from "node:fs";
|
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
11
|
import assert$1 from "assert";
|
|
12
|
-
import { hkdfSync, scrypt } from "crypto";
|
|
12
|
+
import crypto, { hkdfSync, scrypt } from "crypto";
|
|
13
13
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
14
14
|
import fs from "node:fs/promises";
|
|
15
15
|
import { Transform } from "node:stream";
|
|
@@ -3401,6 +3401,179 @@ async function hashPassword(password, salt) {
|
|
|
3401
3401
|
});
|
|
3402
3402
|
return derivedKey;
|
|
3403
3403
|
}
|
|
3404
|
+
const DEFAULT_SCRYPT_N = 8192;
|
|
3405
|
+
const DEFAULT_SCRYPT_R = 8;
|
|
3406
|
+
const DEFAULT_SCRYPT_P = 4;
|
|
3407
|
+
const DEFAULT_SCRYPT_KEYLEN = 64;
|
|
3408
|
+
const DEFAULT_SCRYPT_SALT_BYTES = 16;
|
|
3409
|
+
const DEFAULT_SCRYPT_MAXMEM_BYTES = 256 * 1024 * 1024;
|
|
3410
|
+
const MAX_SCRYPT_MAXMEM_BYTES = 1024 * 1024 * 1024;
|
|
3411
|
+
const MAX_SCRYPT_N = 1048576;
|
|
3412
|
+
const MAX_SCRYPT_R = 64;
|
|
3413
|
+
const MAX_SCRYPT_P = 16;
|
|
3414
|
+
const MAX_SCRYPT_KEYLEN = 128;
|
|
3415
|
+
const MAX_SCRYPT_SALT_BYTES = 64;
|
|
3416
|
+
const parseEnvInt = (value) => {
|
|
3417
|
+
if (typeof value !== "string") return void 0;
|
|
3418
|
+
const trimmed = value.trim();
|
|
3419
|
+
if (!trimmed) return void 0;
|
|
3420
|
+
const parsed = Number(trimmed);
|
|
3421
|
+
if (!Number.isSafeInteger(parsed)) return void 0;
|
|
3422
|
+
return parsed;
|
|
3423
|
+
};
|
|
3424
|
+
const isPowerOfTwo = (value) => (value & value - 1) === 0;
|
|
3425
|
+
const estimateScryptMemoryBytes = ({ N, r, p }) => {
|
|
3426
|
+
return 128 * r * (N + p);
|
|
3427
|
+
};
|
|
3428
|
+
const validateScryptParams = (params) => {
|
|
3429
|
+
const { N, r, p, keylen, saltBytes, maxmemBytes } = params;
|
|
3430
|
+
if (!Number.isSafeInteger(N) || N < 2 || N > MAX_SCRYPT_N || !isPowerOfTwo(N)) {
|
|
3431
|
+
return { ok: false, error: "invalid_scrypt_N" };
|
|
3432
|
+
}
|
|
3433
|
+
if (!Number.isSafeInteger(r) || r < 1 || r > MAX_SCRYPT_R) {
|
|
3434
|
+
return { ok: false, error: "invalid_scrypt_r" };
|
|
3435
|
+
}
|
|
3436
|
+
if (!Number.isSafeInteger(p) || p < 1 || p > MAX_SCRYPT_P) {
|
|
3437
|
+
return { ok: false, error: "invalid_scrypt_p" };
|
|
3438
|
+
}
|
|
3439
|
+
if (!Number.isSafeInteger(keylen) || keylen < 16 || keylen > MAX_SCRYPT_KEYLEN) {
|
|
3440
|
+
return { ok: false, error: "invalid_scrypt_keylen" };
|
|
3441
|
+
}
|
|
3442
|
+
if (!Number.isSafeInteger(saltBytes) || saltBytes < 8 || saltBytes > MAX_SCRYPT_SALT_BYTES) {
|
|
3443
|
+
return { ok: false, error: "invalid_scrypt_salt_bytes" };
|
|
3444
|
+
}
|
|
3445
|
+
if (!Number.isSafeInteger(maxmemBytes) || maxmemBytes < 16 * 1024 * 1024 || maxmemBytes > MAX_SCRYPT_MAXMEM_BYTES) {
|
|
3446
|
+
return { ok: false, error: "invalid_scrypt_maxmem" };
|
|
3447
|
+
}
|
|
3448
|
+
const estimatedMem = estimateScryptMemoryBytes({ N, r, p });
|
|
3449
|
+
if (estimatedMem > maxmemBytes) {
|
|
3450
|
+
return { ok: false, error: "scrypt_params_exceed_maxmem" };
|
|
3451
|
+
}
|
|
3452
|
+
return { ok: true };
|
|
3453
|
+
};
|
|
3454
|
+
const getCurrentMaxmemBytes = (opts) => {
|
|
3455
|
+
const envMaxmemBytes = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_MAXMEM_BYTES);
|
|
3456
|
+
const maxmemBytes = opts?.maxmemBytes ?? envMaxmemBytes ?? DEFAULT_SCRYPT_MAXMEM_BYTES;
|
|
3457
|
+
if (!Number.isSafeInteger(maxmemBytes) || maxmemBytes < 16 * 1024 * 1024 || maxmemBytes > MAX_SCRYPT_MAXMEM_BYTES) {
|
|
3458
|
+
throw new Error("invalid_scrypt_maxmem");
|
|
3459
|
+
}
|
|
3460
|
+
return maxmemBytes;
|
|
3461
|
+
};
|
|
3462
|
+
const getCurrentScryptParams = (opts) => {
|
|
3463
|
+
const envN = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_N);
|
|
3464
|
+
const envR = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_R);
|
|
3465
|
+
const envP = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_P);
|
|
3466
|
+
const envKeylen = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_KEYLEN);
|
|
3467
|
+
const envSaltBytes = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_SALT_BYTES);
|
|
3468
|
+
const params = {
|
|
3469
|
+
N: opts?.N ?? envN ?? DEFAULT_SCRYPT_N,
|
|
3470
|
+
r: opts?.r ?? envR ?? DEFAULT_SCRYPT_R,
|
|
3471
|
+
p: opts?.p ?? envP ?? DEFAULT_SCRYPT_P,
|
|
3472
|
+
keylen: opts?.keylen ?? envKeylen ?? DEFAULT_SCRYPT_KEYLEN,
|
|
3473
|
+
saltBytes: opts?.saltBytes ?? envSaltBytes ?? DEFAULT_SCRYPT_SALT_BYTES,
|
|
3474
|
+
maxmemBytes: getCurrentMaxmemBytes(opts)
|
|
3475
|
+
};
|
|
3476
|
+
const validated = validateScryptParams(params);
|
|
3477
|
+
if (!validated.ok) {
|
|
3478
|
+
throw new Error(validated.error);
|
|
3479
|
+
}
|
|
3480
|
+
return params;
|
|
3481
|
+
};
|
|
3482
|
+
const scryptAsync = async (password, salt, params) => {
|
|
3483
|
+
const { N, r, p, keylen, maxmemBytes } = params;
|
|
3484
|
+
return await new Promise((resolve, reject) => {
|
|
3485
|
+
crypto.scrypt(password, salt, keylen, { N, r, p, maxmem: maxmemBytes }, (err, derivedKey) => {
|
|
3486
|
+
if (err) {
|
|
3487
|
+
reject(err);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
resolve(derivedKey);
|
|
3491
|
+
});
|
|
3492
|
+
});
|
|
3493
|
+
};
|
|
3494
|
+
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
3495
|
+
const parseB64 = (value) => {
|
|
3496
|
+
if (!value) return null;
|
|
3497
|
+
if (!base64Regex.test(value)) return null;
|
|
3498
|
+
if (value.length % 4 !== 0) return null;
|
|
3499
|
+
const buf = Buffer.from(value, "base64");
|
|
3500
|
+
if (buf.length === 0) return null;
|
|
3501
|
+
if (buf.toString("base64") !== value) return null;
|
|
3502
|
+
return buf;
|
|
3503
|
+
};
|
|
3504
|
+
const MAX_STORED_PASSWORD_LENGTH = 1024;
|
|
3505
|
+
const MAX_STORED_PASSWORD_SECTION_LENGTH = 256;
|
|
3506
|
+
const parseStoredScryptHash = (stored) => {
|
|
3507
|
+
if (stored.length > MAX_STORED_PASSWORD_LENGTH) return null;
|
|
3508
|
+
const parts = stored.split("$");
|
|
3509
|
+
if (parts.length !== 5) return null;
|
|
3510
|
+
if (parts[0] !== "" || parts[1] !== "scrypt") return null;
|
|
3511
|
+
const paramsStr = parts[2];
|
|
3512
|
+
const saltB64 = parts[3];
|
|
3513
|
+
const dkB64 = parts[4];
|
|
3514
|
+
if (paramsStr.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
|
|
3515
|
+
if (saltB64.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
|
|
3516
|
+
if (dkB64.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
|
|
3517
|
+
const kvPairs = paramsStr.split(",").filter(Boolean);
|
|
3518
|
+
if (kvPairs.length === 0) return null;
|
|
3519
|
+
const params = /* @__PURE__ */ new Map();
|
|
3520
|
+
for (const pair of kvPairs) {
|
|
3521
|
+
const idx = pair.indexOf("=");
|
|
3522
|
+
if (idx <= 0 || idx === pair.length - 1) return null;
|
|
3523
|
+
const key = pair.slice(0, idx);
|
|
3524
|
+
const valueRaw = pair.slice(idx + 1);
|
|
3525
|
+
if (!/^[a-zA-Z0-9]+$/.test(key)) return null;
|
|
3526
|
+
if (params.has(key)) return null;
|
|
3527
|
+
const value = Number(valueRaw);
|
|
3528
|
+
if (!Number.isSafeInteger(value)) return null;
|
|
3529
|
+
params.set(key, value);
|
|
3530
|
+
}
|
|
3531
|
+
const N = params.get("N");
|
|
3532
|
+
const r = params.get("r");
|
|
3533
|
+
const p = params.get("p");
|
|
3534
|
+
const keylen = params.get("keylen");
|
|
3535
|
+
if (N === void 0 || r === void 0 || p === void 0 || keylen === void 0) return null;
|
|
3536
|
+
if (params.size !== 4) return null;
|
|
3537
|
+
const salt = parseB64(saltB64);
|
|
3538
|
+
const dk = parseB64(dkB64);
|
|
3539
|
+
if (!salt || !dk) return null;
|
|
3540
|
+
if (dk.length !== keylen) return null;
|
|
3541
|
+
if (salt.length < 8 || salt.length > MAX_SCRYPT_SALT_BYTES) return null;
|
|
3542
|
+
if (dk.length < 16 || dk.length > MAX_SCRYPT_KEYLEN) return null;
|
|
3543
|
+
const currentMaxmemBytes = getCurrentMaxmemBytes();
|
|
3544
|
+
const validated = validateScryptParams({
|
|
3545
|
+
N,
|
|
3546
|
+
r,
|
|
3547
|
+
p,
|
|
3548
|
+
keylen,
|
|
3549
|
+
saltBytes: salt.length,
|
|
3550
|
+
maxmemBytes: currentMaxmemBytes
|
|
3551
|
+
});
|
|
3552
|
+
if (!validated.ok) return null;
|
|
3553
|
+
return { N, r, p, keylen, salt, dk };
|
|
3554
|
+
};
|
|
3555
|
+
async function hashPasswordForStorage(password, opts) {
|
|
3556
|
+
const { N, r, p, keylen, saltBytes, maxmemBytes } = getCurrentScryptParams(opts);
|
|
3557
|
+
const salt = crypto.randomBytes(saltBytes);
|
|
3558
|
+
const dk = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
|
|
3559
|
+
const saltB64 = salt.toString("base64");
|
|
3560
|
+
const dkB64 = dk.toString("base64");
|
|
3561
|
+
return `$scrypt$N=${N},r=${r},p=${p},keylen=${keylen}$${saltB64}$${dkB64}`;
|
|
3562
|
+
}
|
|
3563
|
+
async function verifyPasswordFromStorage(password, stored) {
|
|
3564
|
+
const parsed = parseStoredScryptHash(stored);
|
|
3565
|
+
if (!parsed) return false;
|
|
3566
|
+
const { N, r, p, keylen, salt, dk } = parsed;
|
|
3567
|
+
const maxmemBytes = getCurrentMaxmemBytes();
|
|
3568
|
+
let derivedKey;
|
|
3569
|
+
try {
|
|
3570
|
+
derivedKey = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
|
|
3571
|
+
} catch {
|
|
3572
|
+
return false;
|
|
3573
|
+
}
|
|
3574
|
+
if (derivedKey.length !== dk.length) return false;
|
|
3575
|
+
return crypto.timingSafeEqual(dk, derivedKey);
|
|
3576
|
+
}
|
|
3404
3577
|
function createLocation(current, to, state = null, key) {
|
|
3405
3578
|
const location = {
|
|
3406
3579
|
pathname: current,
|
|
@@ -3442,6 +3615,15 @@ const getErrorStatus = (error) => {
|
|
|
3442
3615
|
if (typeof candidate?.response?.status === "number") return candidate.response.status;
|
|
3443
3616
|
return void 0;
|
|
3444
3617
|
};
|
|
3618
|
+
const isResponseLike = (value) => {
|
|
3619
|
+
return Boolean(
|
|
3620
|
+
value && typeof value === "object" && typeof value.status === "number" && value.headers && typeof value.headers.get === "function"
|
|
3621
|
+
);
|
|
3622
|
+
};
|
|
3623
|
+
const isRedirectResponse = (value) => {
|
|
3624
|
+
if (!isResponseLike(value)) return false;
|
|
3625
|
+
return value.status >= 300 && value.status < 400 && Boolean(value.headers.get("Location"));
|
|
3626
|
+
};
|
|
3445
3627
|
async function applyRouteLoaders(req, dataRoutes) {
|
|
3446
3628
|
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
|
3447
3629
|
const url = new URL(req.originalUrl, baseUrl);
|
|
@@ -3486,8 +3668,12 @@ async function applyRouteLoaders(req, dataRoutes) {
|
|
|
3486
3668
|
timeoutId = setTimeout(() => {
|
|
3487
3669
|
const err = new Error(`Loader timeout after ${LOADER_TIMEOUT_MS}ms`);
|
|
3488
3670
|
err.status = 504;
|
|
3489
|
-
console.error("[rpcbase timeout][server loader]", {
|
|
3490
|
-
|
|
3671
|
+
console.error("[rpcbase timeout][server loader]", {
|
|
3672
|
+
routeId: route.id,
|
|
3673
|
+
ms: LOADER_TIMEOUT_MS,
|
|
3674
|
+
url: req.originalUrl
|
|
3675
|
+
});
|
|
3676
|
+
reject({ id: route.id, path: route.path, reason: err });
|
|
3491
3677
|
}, LOADER_TIMEOUT_MS);
|
|
3492
3678
|
});
|
|
3493
3679
|
const loaderPromise = (async () => {
|
|
@@ -3496,9 +3682,9 @@ async function applyRouteLoaders(req, dataRoutes) {
|
|
|
3496
3682
|
params,
|
|
3497
3683
|
ctx: { req }
|
|
3498
3684
|
});
|
|
3499
|
-
return { id: route.id, data };
|
|
3685
|
+
return { id: route.id, path: route.path, data };
|
|
3500
3686
|
} catch (error) {
|
|
3501
|
-
throw { id: route.id, reason: error };
|
|
3687
|
+
throw { id: route.id, path: route.path, reason: error };
|
|
3502
3688
|
} finally {
|
|
3503
3689
|
if (timeoutId) {
|
|
3504
3690
|
clearTimeout(timeoutId);
|
|
@@ -3514,15 +3700,33 @@ async function applyRouteLoaders(req, dataRoutes) {
|
|
|
3514
3700
|
let errors = null;
|
|
3515
3701
|
let hasNotFoundError = false;
|
|
3516
3702
|
let hasNonNotFoundError = false;
|
|
3703
|
+
let redirectResponse = null;
|
|
3704
|
+
let redirectRouteId = null;
|
|
3705
|
+
let redirectRoutePath = null;
|
|
3517
3706
|
for (const result of loaderPromisesResults) {
|
|
3518
3707
|
if (result.status === "fulfilled") {
|
|
3519
3708
|
if (result.value) {
|
|
3520
|
-
|
|
3709
|
+
if (isRedirectResponse(result.value.data)) {
|
|
3710
|
+
redirectResponse = result.value.data;
|
|
3711
|
+
redirectRouteId = result.value.id;
|
|
3712
|
+
redirectRoutePath = result.value.path ?? null;
|
|
3713
|
+
} else {
|
|
3714
|
+
loaderData[result.value.id] = result.value.data;
|
|
3715
|
+
}
|
|
3521
3716
|
}
|
|
3522
3717
|
} else if (result.status === "rejected") {
|
|
3523
3718
|
const id = result.reason?.id;
|
|
3524
3719
|
if (!id) {
|
|
3525
|
-
throw new Error(
|
|
3720
|
+
throw new Error(
|
|
3721
|
+
`missing route ID in error: ${result.reason}`
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
const reasonCandidate = result.reason?.reason ?? result.reason;
|
|
3725
|
+
if (isRedirectResponse(reasonCandidate)) {
|
|
3726
|
+
redirectResponse = reasonCandidate;
|
|
3727
|
+
redirectRouteId = id;
|
|
3728
|
+
redirectRoutePath = result.reason?.path ?? null;
|
|
3729
|
+
continue;
|
|
3526
3730
|
}
|
|
3527
3731
|
if (!errors) {
|
|
3528
3732
|
errors = {};
|
|
@@ -3536,6 +3740,19 @@ async function applyRouteLoaders(req, dataRoutes) {
|
|
|
3536
3740
|
errors[id] = result.reason;
|
|
3537
3741
|
}
|
|
3538
3742
|
}
|
|
3743
|
+
if (redirectResponse) {
|
|
3744
|
+
return {
|
|
3745
|
+
...baseContext,
|
|
3746
|
+
matches,
|
|
3747
|
+
loaderData,
|
|
3748
|
+
actionData: null,
|
|
3749
|
+
errors: null,
|
|
3750
|
+
statusCode: redirectResponse.status,
|
|
3751
|
+
redirectResponse,
|
|
3752
|
+
redirectRouteId,
|
|
3753
|
+
redirectRoutePath
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3539
3756
|
let statusCode = 200;
|
|
3540
3757
|
if (errors) {
|
|
3541
3758
|
if (hasNonNotFoundError) {
|
|
@@ -3556,19 +3773,46 @@ async function applyRouteLoaders(req, dataRoutes) {
|
|
|
3556
3773
|
};
|
|
3557
3774
|
}
|
|
3558
3775
|
async function renderSSR(req, dataRoutes) {
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3776
|
+
const routerContext = await applyRouteLoaders(req, dataRoutes);
|
|
3777
|
+
const isMatched = routerContext.matches.length > 0;
|
|
3778
|
+
if (routerContext.redirectResponse) {
|
|
3779
|
+
return {
|
|
3780
|
+
element: null,
|
|
3781
|
+
isMatched,
|
|
3782
|
+
statusCode: routerContext.statusCode ?? routerContext.redirectResponse.status ?? 302,
|
|
3783
|
+
redirectResponse: routerContext.redirectResponse,
|
|
3784
|
+
redirectRouteId: routerContext.redirectRouteId,
|
|
3785
|
+
redirectRoutePath: routerContext.redirectRoutePath
|
|
3786
|
+
};
|
|
3565
3787
|
}
|
|
3566
3788
|
if (routerContext.errors) {
|
|
3567
3789
|
if (routerContext.statusCode === 404) {
|
|
3568
|
-
console.warn(`SSR 404 ${req.method} ${req.originalUrl}`, {
|
|
3790
|
+
console.warn(`SSR 404 ${req.method} ${req.originalUrl}`, {
|
|
3791
|
+
errors: routerContext.errors
|
|
3792
|
+
});
|
|
3569
3793
|
} else {
|
|
3570
|
-
|
|
3571
|
-
|
|
3794
|
+
const matchesSummary = routerContext.matches?.map((m) => ({
|
|
3795
|
+
routeId: m.route.id,
|
|
3796
|
+
routePath: m.route.path,
|
|
3797
|
+
pathname: m.pathname,
|
|
3798
|
+
pathnameBase: m.pathnameBase
|
|
3799
|
+
}));
|
|
3800
|
+
console.error(
|
|
3801
|
+
`SSR ${routerContext.statusCode || 500} ${req.method} ${req.originalUrl}`,
|
|
3802
|
+
{
|
|
3803
|
+
errors: routerContext.errors,
|
|
3804
|
+
matches: matchesSummary
|
|
3805
|
+
}
|
|
3806
|
+
);
|
|
3807
|
+
const errorEntries = Object.entries(routerContext.errors || {});
|
|
3808
|
+
const firstErrorEntry = errorEntries[0]?.[1];
|
|
3809
|
+
const firstReason = firstErrorEntry?.reason ?? firstErrorEntry;
|
|
3810
|
+
if (errorEntries.length === 1 && firstReason instanceof Error) {
|
|
3811
|
+
firstReason.details = routerContext.errors;
|
|
3812
|
+
throw firstReason;
|
|
3813
|
+
}
|
|
3814
|
+
const extra = firstErrorEntry?.id ? ` (route ${firstErrorEntry.id}${firstErrorEntry?.path ? ` ${firstErrorEntry.path}` : ""})` : "";
|
|
3815
|
+
const error = new Error(`SSR loader error${extra}`);
|
|
3572
3816
|
error.details = routerContext.errors;
|
|
3573
3817
|
throw error;
|
|
3574
3818
|
}
|
|
@@ -3581,8 +3825,11 @@ async function renderSSR(req, dataRoutes) {
|
|
|
3581
3825
|
context: routerContext
|
|
3582
3826
|
}
|
|
3583
3827
|
) });
|
|
3584
|
-
|
|
3585
|
-
|
|
3828
|
+
return {
|
|
3829
|
+
element,
|
|
3830
|
+
isMatched,
|
|
3831
|
+
statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200)
|
|
3832
|
+
};
|
|
3586
3833
|
}
|
|
3587
3834
|
const ABORT_DELAY_MS = 1e4;
|
|
3588
3835
|
const APP_HTML_PLACEHOLDER = "<!--app-html-->";
|
|
@@ -3591,6 +3838,33 @@ const FALLBACK_ERROR_TEMPLATE_START = `<!doctype html><html lang="en"><head><met
|
|
|
3591
3838
|
const FALLBACK_ERROR_TEMPLATE_END = "</main></body></html>";
|
|
3592
3839
|
const isProduction = env.NODE_ENV === "production";
|
|
3593
3840
|
const templateHtml = isProduction ? readFileSync("./build/dist/client/src/client/index.html", "utf-8") : "";
|
|
3841
|
+
const handleRedirectionResponse = (res, redirectResponse, location) => {
|
|
3842
|
+
res.status(redirectResponse.status || 302);
|
|
3843
|
+
try {
|
|
3844
|
+
const headers = redirectResponse.headers;
|
|
3845
|
+
const setCookies = headers.getSetCookie?.();
|
|
3846
|
+
const fallbackSetCookies = [];
|
|
3847
|
+
for (const [key, value] of headers) {
|
|
3848
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
3849
|
+
if (!setCookies?.length) {
|
|
3850
|
+
fallbackSetCookies.push(value);
|
|
3851
|
+
}
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
res.setHeader(key, value);
|
|
3855
|
+
}
|
|
3856
|
+
if (setCookies?.length) {
|
|
3857
|
+
res.setHeader("Set-Cookie", setCookies);
|
|
3858
|
+
} else if (fallbackSetCookies.length === 1) {
|
|
3859
|
+
res.setHeader("Set-Cookie", fallbackSetCookies[0]);
|
|
3860
|
+
} else if (fallbackSetCookies.length > 1) {
|
|
3861
|
+
res.setHeader("Set-Cookie", fallbackSetCookies);
|
|
3862
|
+
}
|
|
3863
|
+
} catch {
|
|
3864
|
+
}
|
|
3865
|
+
if (location) res.setHeader("Location", location);
|
|
3866
|
+
res.end();
|
|
3867
|
+
};
|
|
3594
3868
|
const formatErrorDetails = (error) => {
|
|
3595
3869
|
if (isProduction) return void 0;
|
|
3596
3870
|
if (!error) return void 0;
|
|
@@ -3701,7 +3975,32 @@ const ssrMiddleware = ({
|
|
|
3701
3975
|
htmlStart = templateStart;
|
|
3702
3976
|
}
|
|
3703
3977
|
htmlEnd = template.slice(placeholderIndex + APP_HTML_PLACEHOLDER.length);
|
|
3704
|
-
const {
|
|
3978
|
+
const {
|
|
3979
|
+
element,
|
|
3980
|
+
isMatched,
|
|
3981
|
+
statusCode,
|
|
3982
|
+
redirectResponse,
|
|
3983
|
+
redirectRouteId,
|
|
3984
|
+
redirectRoutePath
|
|
3985
|
+
} = await renderSSR(req, dataRoutes);
|
|
3986
|
+
if (redirectResponse) {
|
|
3987
|
+
if (!responseCommitted) {
|
|
3988
|
+
const location = redirectResponse.headers?.get?.("Location");
|
|
3989
|
+
if (!isProduction) {
|
|
3990
|
+
console.info("SSR redirect", {
|
|
3991
|
+
method: req.method,
|
|
3992
|
+
url: req.originalUrl,
|
|
3993
|
+
status: redirectResponse.status,
|
|
3994
|
+
location,
|
|
3995
|
+
routeId: redirectRouteId ?? void 0,
|
|
3996
|
+
routePath: redirectRoutePath ?? void 0
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3999
|
+
responseCommitted = true;
|
|
4000
|
+
handleRedirectionResponse(res, redirectResponse, location);
|
|
4001
|
+
}
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
3705
4004
|
if (!isMatched) {
|
|
3706
4005
|
next();
|
|
3707
4006
|
return;
|
|
@@ -3795,7 +4094,9 @@ const sendEmail = async (params) => {
|
|
|
3795
4094
|
export {
|
|
3796
4095
|
getDerivedKey,
|
|
3797
4096
|
hashPassword,
|
|
4097
|
+
hashPasswordForStorage,
|
|
3798
4098
|
initServer,
|
|
3799
4099
|
sendEmail,
|
|
3800
|
-
ssrMiddleware
|
|
4100
|
+
ssrMiddleware,
|
|
4101
|
+
verifyPasswordFromStorage
|
|
3801
4102
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type HashPasswordForStorageOptions = Partial<{
|
|
2
|
+
N: number;
|
|
3
|
+
r: number;
|
|
4
|
+
p: number;
|
|
5
|
+
keylen: number;
|
|
6
|
+
saltBytes: number;
|
|
7
|
+
maxmemBytes: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function hashPasswordForStorage(password: string, opts?: HashPasswordForStorageOptions): Promise<string>;
|
|
10
|
+
export declare function verifyPasswordFromStorage(password: string, stored: string): Promise<boolean>;
|
|
11
|
+
//# sourceMappingURL=passwordHashStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passwordHashStorage.d.ts","sourceRoot":"","sources":["../src/passwordHashStorage.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,6BAA6B,GAAG,OAAO,CAAC;IAClD,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AA4MF,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAUpH;AAED,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBlG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passwordHashStorage.test.d.ts","sourceRoot":"","sources":["../src/passwordHashStorage.test.ts"],"names":[],"mappings":""}
|
package/dist/renderSSR.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { ReactNode } from 'react';
|
|
|
2
2
|
import { StaticHandler } from '../../router/src';
|
|
3
3
|
import * as express from "express";
|
|
4
4
|
export declare function renderSSR(req: express.Request, dataRoutes: StaticHandler["dataRoutes"]): Promise<{
|
|
5
|
-
element: ReactNode;
|
|
5
|
+
element: ReactNode | null;
|
|
6
6
|
isMatched: boolean;
|
|
7
7
|
statusCode: number;
|
|
8
|
+
redirectResponse?: Response;
|
|
9
|
+
redirectRouteId?: string | null;
|
|
10
|
+
redirectRoutePath?: string | null;
|
|
8
11
|
}>;
|
|
9
12
|
//# sourceMappingURL=renderSSR.d.ts.map
|
package/dist/renderSSR.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderSSR.d.ts","sourceRoot":"","sources":["../src/renderSSR.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAc,MAAM,OAAO,CAAA;AAC7C,OAAO,EAGL,aAAa,EACd,MAAM,iBAAiB,CAAA;AAOxB,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,GACtC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"renderSSR.d.ts","sourceRoot":"","sources":["../src/renderSSR.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAc,MAAM,OAAO,CAAA;AAC7C,OAAO,EAGL,aAAa,EACd,MAAM,iBAAiB,CAAA;AAOxB,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,GACtC,OAAO,CAAC;IACT,OAAO,EAAE,SAAS,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,QAAQ,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAC,CAwED"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssrMiddleware.d.ts","sourceRoot":"","sources":["../src/ssrMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAwE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACjI,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAErE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAM3C,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ssrMiddleware.d.ts","sourceRoot":"","sources":["../src/ssrMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAwE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACjI,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAErE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAM3C,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAkI9D,eAAO,MAAM,aAAa,GAAI,kFAM3B;IACD,YAAY,EAAE,aAAa,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACxC,mBAAmB,CAAC,EAAE,aAAa,CAAC;QAAE,KAAK,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;IACtE,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B,MAAW,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBA0K1D,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpcbase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.463.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "wireit",
|
|
12
|
+
"test": "wireit",
|
|
12
13
|
"release": "wireit"
|
|
13
14
|
},
|
|
14
15
|
"wireit": {
|
|
@@ -30,6 +31,15 @@
|
|
|
30
31
|
"command": "../../node_modules/.bin/vite build --watch",
|
|
31
32
|
"service": true
|
|
32
33
|
},
|
|
34
|
+
"test": {
|
|
35
|
+
"command": "../../node_modules/.bin/vitest run --config ../../pkg/test/src/vitest.config.mjs --passWithNoTests",
|
|
36
|
+
"files": [
|
|
37
|
+
"src/**/*",
|
|
38
|
+
"tsconfig.json",
|
|
39
|
+
"../../scripts/tsconfig.pkg.json"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": []
|
|
42
|
+
},
|
|
33
43
|
"release": {
|
|
34
44
|
"command": "../../scripts/publish.js",
|
|
35
45
|
"dependencies": [
|