@server/next 0.45.1 → 0.46.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/index.d.ts +2 -2
- package/index.js +37 -12
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -255,7 +255,7 @@ type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
|
|
|
255
255
|
type OnResponse = (response: Response, ctx: Context) => Response | void | Promise<Response | void>;
|
|
256
256
|
type Options = {
|
|
257
257
|
port?: number;
|
|
258
|
-
|
|
258
|
+
secrets?: string | string[];
|
|
259
259
|
public?: string | Bucket;
|
|
260
260
|
uploads?: string | Bucket | UploadOptions;
|
|
261
261
|
cors?: CorsOptions;
|
|
@@ -275,7 +275,7 @@ type Options = {
|
|
|
275
275
|
};
|
|
276
276
|
type Settings = {
|
|
277
277
|
port: number;
|
|
278
|
-
|
|
278
|
+
secrets: string[];
|
|
279
279
|
public?: Bucket;
|
|
280
280
|
uploads?: ({
|
|
281
281
|
bucket: Bucket;
|
package/index.js
CHANGED
|
@@ -1040,13 +1040,17 @@ async function verifyJwt(token, secret) {
|
|
|
1040
1040
|
return null;
|
|
1041
1041
|
}
|
|
1042
1042
|
if (header?.alg !== "HS256") return null;
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1043
|
+
let ok = false;
|
|
1044
|
+
for (const candidate of Array.isArray(secret) ? secret : [secret]) {
|
|
1045
|
+
const key = await hmacKey(candidate);
|
|
1046
|
+
ok = await crypto.subtle.verify(
|
|
1047
|
+
"HMAC",
|
|
1048
|
+
key,
|
|
1049
|
+
unb64url(sig),
|
|
1050
|
+
enc.encode(`${head}.${body}`)
|
|
1051
|
+
);
|
|
1052
|
+
if (ok) break;
|
|
1053
|
+
}
|
|
1050
1054
|
if (!ok) return null;
|
|
1051
1055
|
let payload;
|
|
1052
1056
|
try {
|
|
@@ -1102,7 +1106,11 @@ async function finishLogin(ctx, input, opts = {}) {
|
|
|
1102
1106
|
provider: input.provider
|
|
1103
1107
|
};
|
|
1104
1108
|
assertUser(payload, "onToken");
|
|
1105
|
-
const token = await signJwt(
|
|
1109
|
+
const token = await signJwt(
|
|
1110
|
+
payload,
|
|
1111
|
+
ctx.options.secrets[0],
|
|
1112
|
+
7 * 24 * 60 * 60
|
|
1113
|
+
);
|
|
1106
1114
|
const exposed = await onUser(payload, ctx);
|
|
1107
1115
|
assertUser(exposed, "onUser");
|
|
1108
1116
|
return status(201).json({ ...exposed, token });
|
|
@@ -1754,6 +1762,13 @@ function createLogger(level) {
|
|
|
1754
1762
|
};
|
|
1755
1763
|
}
|
|
1756
1764
|
|
|
1765
|
+
// src/helpers/secrets.ts
|
|
1766
|
+
function resolveSecrets(option) {
|
|
1767
|
+
const given = option ?? globalThis.env.SECRETS?.split(",");
|
|
1768
|
+
const list = (Array.isArray(given) ? given : [given]).map((one) => one?.trim()).filter(Boolean);
|
|
1769
|
+
return list.length ? list : [`unsafe-${createId()}`];
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1757
1772
|
// src/helpers/security.ts
|
|
1758
1773
|
function resolveSecurity(security) {
|
|
1759
1774
|
const off = security === false;
|
|
@@ -1827,13 +1842,23 @@ function config(options = {}) {
|
|
|
1827
1842
|
);
|
|
1828
1843
|
}
|
|
1829
1844
|
}
|
|
1845
|
+
if (opts.secret !== void 0) {
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
"The `secret` option is now `secrets`, and takes one key or several: `secrets: [current, previous]` signs with the first and verifies with any, so rotating a key no longer signs everyone out."
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
if (env2.SECRET && !env2.SECRETS) {
|
|
1851
|
+
throw new Error(
|
|
1852
|
+
"The SECRET environment variable is now SECRETS, a comma-separated list. Rename it, or every token signed with the old key breaks."
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1830
1855
|
const raw = options.log ?? env2.LOG_LEVEL;
|
|
1831
1856
|
const level = raw === true ? "info" : raw === false ? void 0 : raw;
|
|
1832
1857
|
const log = createLogger(level);
|
|
1833
1858
|
const settings = {
|
|
1834
1859
|
// `env.PORT` is a string, so coerce it: `settings.port` is a number
|
|
1835
1860
|
port: options.port || Number(env2.PORT) || 3e3,
|
|
1836
|
-
|
|
1861
|
+
secrets: resolveSecrets(options.secrets),
|
|
1837
1862
|
log,
|
|
1838
1863
|
// How request bodies are read: parsed into ctx.body by default; `raw` keeps
|
|
1839
1864
|
// the Buffer, `stream` hands the handler the unread web ReadableStream.
|
|
@@ -1906,9 +1931,9 @@ function config(options = {}) {
|
|
|
1906
1931
|
settings.auth.sessions = toStoreExpiring(/* @__PURE__ */ new Map(), "1w");
|
|
1907
1932
|
}
|
|
1908
1933
|
}
|
|
1909
|
-
if (settings.auth?.strategy.includes("jwt") && settings.
|
|
1934
|
+
if (settings.auth?.strategy.includes("jwt") && settings.secrets[0].startsWith("unsafe-")) {
|
|
1910
1935
|
console.warn(
|
|
1911
|
-
"[server:auth] jwt strategy with no
|
|
1936
|
+
"[server:auth] jwt strategy with no SECRETS set: tokens are signed with a random per-process secret, so they break on restart and across instances. Set the SECRETS environment variable (or the `secrets` option)."
|
|
1912
1937
|
);
|
|
1913
1938
|
}
|
|
1914
1939
|
if (options.openapi) {
|
|
@@ -2373,7 +2398,7 @@ async function getJwtUser(ctx) {
|
|
|
2373
2398
|
if (type2?.toLowerCase() !== "bearer" || !token) {
|
|
2374
2399
|
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
2375
2400
|
}
|
|
2376
|
-
const payload = await verifyJwt(token, ctx.options.
|
|
2401
|
+
const payload = await verifyJwt(token, ctx.options.secrets);
|
|
2377
2402
|
if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
2378
2403
|
const { iat, exp, ...claims } = payload;
|
|
2379
2404
|
if (!claims.id || !claims.email) throw ServerError_default.AUTH_INVALID_TOKEN();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
|
|
5
5
|
"homepage": "https://server-js.com/",
|
|
6
6
|
"repository": "github:franciscop/server-next",
|