@server/next 0.44.1 → 0.45.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/index.d.ts +15 -17
- package/index.js +96 -189
- package/package.json +4 -2
- package/readme.md +12 -4
- package/src/jsx/jsx-runtime.d.ts +26 -8
- package/src/jsx/jsx-runtime.js +8 -5
- package/src/jsx/jsx-runtime.test.jsx +0 -694
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
2
3
|
export { default as kv } from 'polystore';
|
|
3
4
|
export { default as bucket } from 'bucket';
|
|
4
5
|
|
|
@@ -9,6 +10,7 @@ type LimitOptions = {
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
type CookieOptions = string | string[] | Cookie | Cookie[] | null;
|
|
13
|
+
type SendBody = SerializableValue | JSX.Element | Uint8Array | ReadableStream | Readable | Response | Reply$1 | BucketFile | Promise<SendBody>;
|
|
12
14
|
interface ResponseData {
|
|
13
15
|
headers: Headers;
|
|
14
16
|
status?: number;
|
|
@@ -22,10 +24,10 @@ declare class Reply$1 {
|
|
|
22
24
|
headers(key: string | Record<string, string | string[]>, value?: string | string[]): this;
|
|
23
25
|
cache(value: CacheOption): this;
|
|
24
26
|
cookies(key: string | Record<string, CookieOptions>, value?: CookieOptions): this;
|
|
25
|
-
json(body: unknown): Response
|
|
26
|
-
redirect(path: string): Response
|
|
27
|
+
json(body: unknown): Promise<Response>;
|
|
28
|
+
redirect(path: string): Promise<Response>;
|
|
27
29
|
file(path: string | BucketFile): Promise<Response>;
|
|
28
|
-
send(
|
|
30
|
+
send(input?: SendBody): Promise<Response>;
|
|
29
31
|
}
|
|
30
32
|
type Params<K extends keyof Reply$1> = Reply$1[K] extends (...args: infer A) => any ? A : never;
|
|
31
33
|
declare const status: (...args: Params<"status">) => Reply$1;
|
|
@@ -34,25 +36,23 @@ declare const type: (...args: Params<"type">) => Reply$1;
|
|
|
34
36
|
declare const cache: (...args: Params<"cache">) => Reply$1;
|
|
35
37
|
declare const download: (...args: Params<"download">) => Reply$1;
|
|
36
38
|
declare const cookies: (...args: Params<"cookies">) => Reply$1;
|
|
37
|
-
declare const send: (...args: Params<"send">) => Response
|
|
38
|
-
declare const json: (...args: Params<"json">) => Response
|
|
39
|
+
declare const send: (...args: Params<"send">) => Promise<Response>;
|
|
40
|
+
declare const json: (...args: Params<"json">) => Promise<Response>;
|
|
39
41
|
declare const file: (...args: Params<"file">) => Promise<Response>;
|
|
40
|
-
declare const redirect: (...args: Params<"redirect">) => Response
|
|
42
|
+
declare const redirect: (...args: Params<"redirect">) => Promise<Response>;
|
|
41
43
|
|
|
42
44
|
type Reply = ReturnType<typeof status>;
|
|
43
45
|
type Method = "get" | "post" | "put" | "patch" | "delete" | "head" | "options" | "socket";
|
|
44
46
|
type ContextTypes = {
|
|
45
|
-
session?: any;
|
|
46
47
|
user?: any;
|
|
47
48
|
params?: any;
|
|
48
49
|
query?: any;
|
|
49
50
|
body?: any;
|
|
50
51
|
};
|
|
51
52
|
type Field<C, K extends keyof ContextTypes, Fallback> = K extends keyof C ? SchemaOutput<C[K], C[K]> : Fallback;
|
|
52
|
-
declare namespace JSX {
|
|
53
|
+
declare namespace JSX$1 {
|
|
53
54
|
interface Element {
|
|
54
|
-
|
|
55
|
-
props: any;
|
|
55
|
+
(): string;
|
|
56
56
|
}
|
|
57
57
|
interface IntrinsicElements {
|
|
58
58
|
[elem: string]: any;
|
|
@@ -203,6 +203,7 @@ type AuthOption = `${Strategy}:${Provider}` | {
|
|
|
203
203
|
strategy: Strategy;
|
|
204
204
|
providers?: Provider | Provider[];
|
|
205
205
|
users?: StoreSource;
|
|
206
|
+
sessions?: StoreSource;
|
|
206
207
|
redirect?: string;
|
|
207
208
|
onProfile?: (raw: any, provider: Provider) => ProfileUser | Promise<ProfileUser>;
|
|
208
209
|
onLogin?: (loginUser: AuthUser, existingUser: AuthUser | null, ctx: Context) => ProfileUser | Promise<ProfileUser>;
|
|
@@ -214,6 +215,7 @@ type AuthSettings = {
|
|
|
214
215
|
providers: Provider[];
|
|
215
216
|
strategy: Strategy;
|
|
216
217
|
users: KVStore;
|
|
218
|
+
sessions: KVStore;
|
|
217
219
|
onProfile?: (raw: any, provider: Provider) => ProfileUser | Promise<ProfileUser>;
|
|
218
220
|
onLogin?: (loginUser: AuthUser, existingUser: AuthUser | null, ctx: Context) => ProfileUser | Promise<ProfileUser>;
|
|
219
221
|
onUser: <T = AuthUser>(user: T, ctx: Context) => T | Promise<T>;
|
|
@@ -256,7 +258,6 @@ type Options = {
|
|
|
256
258
|
secret?: string;
|
|
257
259
|
public?: string | Bucket;
|
|
258
260
|
uploads?: string | Bucket | UploadOptions;
|
|
259
|
-
sessions?: StoreSource;
|
|
260
261
|
cors?: CorsOptions;
|
|
261
262
|
auth?: AuthOption;
|
|
262
263
|
openapi?: boolean | string | {
|
|
@@ -279,8 +280,6 @@ type Settings = {
|
|
|
279
280
|
uploads?: ({
|
|
280
281
|
bucket: Bucket;
|
|
281
282
|
} & LimitOptions) | null;
|
|
282
|
-
sessions: KVStore;
|
|
283
|
-
sessionsDefault?: boolean;
|
|
284
283
|
cors?: CorsSettings;
|
|
285
284
|
auth?: AuthSettings;
|
|
286
285
|
openapi?: {
|
|
@@ -339,7 +338,6 @@ type Context<C extends ContextTypes = {}> = {
|
|
|
339
338
|
time?: Time;
|
|
340
339
|
socket?: WebSocket;
|
|
341
340
|
sockets?: WebSocket[];
|
|
342
|
-
session: Field<C, "session", Record<string, any>>;
|
|
343
341
|
user?: Field<C, "user", Record<string, any>>;
|
|
344
342
|
init: number;
|
|
345
343
|
app: Server;
|
|
@@ -351,7 +349,7 @@ type Context<C extends ContextTypes = {}> = {
|
|
|
351
349
|
type InlineReply = Response | Reply | BucketFile | {
|
|
352
350
|
body: string;
|
|
353
351
|
headers?: Headers;
|
|
354
|
-
} | SerializableValue | JSX.Element | Buffer | ReadableStream;
|
|
352
|
+
} | SerializableValue | JSX$1.Element | Buffer | ReadableStream;
|
|
355
353
|
type Body = InlineReply;
|
|
356
354
|
type Middleware<C extends ContextTypes = {}> = (ctx: Context<C>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
|
|
357
355
|
|
|
@@ -373,7 +371,7 @@ declare class ServerError extends Error {
|
|
|
373
371
|
declare const TypedServerError: typeof ServerError & ServerErrorConstructor;
|
|
374
372
|
|
|
375
373
|
declare global {
|
|
376
|
-
var env: Record<string,
|
|
374
|
+
var env: Record<string, string | undefined>;
|
|
377
375
|
}
|
|
378
376
|
|
|
379
377
|
type Fn<C extends ContextTypes> = (ctx: Context<C>) => ReturnType<Middleware>;
|
|
@@ -440,7 +438,7 @@ declare class ValidationError extends StatusError {
|
|
|
440
438
|
declare class Server<C extends ContextTypes = {}> extends Router<C> {
|
|
441
439
|
settings: Settings;
|
|
442
440
|
platform: Platform;
|
|
443
|
-
sockets:
|
|
441
|
+
sockets: WebSocket[];
|
|
444
442
|
websocket: any;
|
|
445
443
|
port?: number;
|
|
446
444
|
constructor(options?: Options);
|
package/index.js
CHANGED
|
@@ -49,8 +49,6 @@ ServerError_default.extend({
|
|
|
49
49
|
status: 400,
|
|
50
50
|
message: "Missing the OAuth 'code' in the request body"
|
|
51
51
|
},
|
|
52
|
-
SESSION_JWT: "The `jwt` strategy is stateless, so there is no `ctx.session` (tried '{key}'). Use the `token` strategy for server-side sessions, or `cookie` for browsers",
|
|
53
|
-
SESSION_GUEST: "No `ctx.session` for this request (tried '{key}'): the `token` strategy carries the session in the Authorization header, and this request has none. Sign in first, or use the `cookie` strategy for guest sessions",
|
|
54
52
|
AUTH_INVALID_HEADER: {
|
|
55
53
|
status: 401,
|
|
56
54
|
message: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)"
|
|
@@ -799,7 +797,7 @@ function isReadableStream(obj) {
|
|
|
799
797
|
|
|
800
798
|
// src/reply.ts
|
|
801
799
|
var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
|
|
802
|
-
var Reply = class {
|
|
800
|
+
var Reply = class _Reply {
|
|
803
801
|
res;
|
|
804
802
|
constructor() {
|
|
805
803
|
this.res = {
|
|
@@ -870,10 +868,12 @@ var Reply = class {
|
|
|
870
868
|
}
|
|
871
869
|
async file(path) {
|
|
872
870
|
if (typeof path !== "string") {
|
|
873
|
-
if (!await path.exists()) return
|
|
871
|
+
if (!await path.exists()) return new Response(null, { status: 404 });
|
|
874
872
|
return this.type(fileType(path)).send(path.stream());
|
|
875
873
|
}
|
|
876
|
-
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(path))
|
|
874
|
+
if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(path)) {
|
|
875
|
+
return new Response(null, { status: 404 });
|
|
876
|
+
}
|
|
877
877
|
try {
|
|
878
878
|
const fs = await import("fs");
|
|
879
879
|
const ext = path.split(".").pop();
|
|
@@ -882,23 +882,55 @@ var Reply = class {
|
|
|
882
882
|
return this.type(ext).send(stream);
|
|
883
883
|
} catch (error) {
|
|
884
884
|
if (error.code === "ENOENT" || error.code === "EISDIR") {
|
|
885
|
-
return
|
|
885
|
+
return new Response(null, { status: 404 });
|
|
886
886
|
}
|
|
887
887
|
throw error;
|
|
888
888
|
}
|
|
889
889
|
}
|
|
890
|
-
send(
|
|
890
|
+
// Accepts everything a route can return, so `send(x)` and `return x` agree.
|
|
891
|
+
// Async because a bucket file has to be read before its status is known;
|
|
892
|
+
// routes await whatever they return, so this is invisible in normal use.
|
|
893
|
+
async send(input = "") {
|
|
891
894
|
const { status: status2 = 200, headers: headers2 } = this.res;
|
|
895
|
+
let body = input;
|
|
892
896
|
if (status2 === 101 || status2 === 204 || status2 === 205 || status2 === 304) {
|
|
893
897
|
return new Response(null, { status: status2, headers: headers2 });
|
|
894
898
|
}
|
|
895
899
|
if (body === null) body = "";
|
|
900
|
+
if (typeof body?.then === "function") body = await body;
|
|
896
901
|
if (typeof body === "function") body = body();
|
|
897
902
|
if (typeof body?.then === "function") {
|
|
898
903
|
throw new Error(
|
|
899
|
-
"
|
|
904
|
+
"Cannot render an async component: components must be synchronous. Await the data before rendering and pass it in as props."
|
|
900
905
|
);
|
|
901
906
|
}
|
|
907
|
+
if (body instanceof _Reply) body = await body.send();
|
|
908
|
+
if (body instanceof Response) {
|
|
909
|
+
const merged = new Headers(body.headers);
|
|
910
|
+
for (const [key, value] of headers2) {
|
|
911
|
+
if (key === "set-cookie") continue;
|
|
912
|
+
merged.set(key, value);
|
|
913
|
+
}
|
|
914
|
+
for (const cookie of headers2.getSetCookie?.() ?? []) {
|
|
915
|
+
merged.append("set-cookie", cookie);
|
|
916
|
+
}
|
|
917
|
+
if (body.url && /^(br|gzip)$/.test(merged.get("content-encoding") || "")) {
|
|
918
|
+
merged.delete("content-encoding");
|
|
919
|
+
}
|
|
920
|
+
return new Response(body.body, {
|
|
921
|
+
status: this.res.status ?? body.status,
|
|
922
|
+
headers: merged
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
if (body && typeof body.stream === "function" && typeof body.bytes === "function" && typeof body.exists === "function" && typeof body.name === "string") {
|
|
926
|
+
return this.file(body);
|
|
927
|
+
}
|
|
928
|
+
if (body instanceof Blob) {
|
|
929
|
+
if (!headers2.get("content-type") && body.type) {
|
|
930
|
+
headers2.set("content-type", body.type);
|
|
931
|
+
}
|
|
932
|
+
return new Response(body, { status: status2, headers: headers2 });
|
|
933
|
+
}
|
|
902
934
|
if (typeof body === "string") {
|
|
903
935
|
if (!headers2.get("content-type")) {
|
|
904
936
|
headers2.set("content-type", isHtml(body) ? mimes_default.html : mimes_default.text);
|
|
@@ -924,6 +956,12 @@ var Reply = class {
|
|
|
924
956
|
if (isReadableStream(body)) {
|
|
925
957
|
return new Response(toWeb(body), { status: status2, headers: headers2 });
|
|
926
958
|
}
|
|
959
|
+
if (!Array.isArray(body) && body?.[Symbol.iterator]) {
|
|
960
|
+
return new Response(iteratorToReadable(body), { status: status2, headers: headers2 });
|
|
961
|
+
}
|
|
962
|
+
if (body?.[Symbol.asyncIterator]) {
|
|
963
|
+
return new Response(iteratorAsyncToReadable(body), { status: status2, headers: headers2 });
|
|
964
|
+
}
|
|
927
965
|
if (!headers2.get("content-type")) {
|
|
928
966
|
headers2.set("content-type", "application/json");
|
|
929
967
|
}
|
|
@@ -1039,48 +1077,11 @@ function findSessionId(ctx) {
|
|
|
1039
1077
|
return ctx.cookies.session || void 0;
|
|
1040
1078
|
}
|
|
1041
1079
|
|
|
1042
|
-
// src/middle/session.ts
|
|
1043
|
-
var loaded = /* @__PURE__ */ new WeakMap();
|
|
1044
|
-
function noSession(error) {
|
|
1045
|
-
const target = {};
|
|
1046
|
-
return new Proxy(target, {
|
|
1047
|
-
get(target2, key) {
|
|
1048
|
-
if (typeof key === "symbol" || key === "then") return target2[key];
|
|
1049
|
-
throw error(String(key));
|
|
1050
|
-
},
|
|
1051
|
-
set(target2, key, value) {
|
|
1052
|
-
if (typeof key === "symbol") {
|
|
1053
|
-
target2[key] = value;
|
|
1054
|
-
return true;
|
|
1055
|
-
}
|
|
1056
|
-
throw error(String(key));
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
async function session(ctx) {
|
|
1061
|
-
const strategy = ctx.options.auth?.strategy;
|
|
1062
|
-
if (strategy?.includes("jwt")) {
|
|
1063
|
-
ctx.session = noSession((key) => ServerError_default.SESSION_JWT({ key }));
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
const id = findSessionId(ctx);
|
|
1067
|
-
if (!id && strategy?.includes("token")) {
|
|
1068
|
-
ctx.session = noSession((key) => ServerError_default.SESSION_GUEST({ key }));
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
ctx.session = id && await ctx.options.sessions.get(id) || {};
|
|
1072
|
-
loaded.set(ctx, { id, data: JSON.stringify(ctx.session) });
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
1080
|
// src/auth/finishLogin.ts
|
|
1076
1081
|
async function finishLogin(ctx, input, opts = {}) {
|
|
1077
1082
|
const settings = ctx.options.auth;
|
|
1078
1083
|
const { strategy, onLogin, onUser, onToken } = settings;
|
|
1079
1084
|
const key = String(input.key);
|
|
1080
|
-
if (!strategy.includes("jwt") && !loaded.has(ctx)) {
|
|
1081
|
-
ctx.session = {};
|
|
1082
|
-
loaded.set(ctx, { id: void 0, data: "{}" });
|
|
1083
|
-
}
|
|
1084
1085
|
const auth2 = {
|
|
1085
1086
|
user: key,
|
|
1086
1087
|
provider: input.provider,
|
|
@@ -1106,12 +1107,10 @@ async function finishLogin(ctx, input, opts = {}) {
|
|
|
1106
1107
|
assertUser(exposed, "onUser");
|
|
1107
1108
|
return status(201).json({ ...exposed, token });
|
|
1108
1109
|
}
|
|
1109
|
-
const prev =
|
|
1110
|
-
if (prev
|
|
1110
|
+
const prev = findSessionId(ctx);
|
|
1111
|
+
if (prev) await settings.sessions.del(prev);
|
|
1111
1112
|
const id = createId();
|
|
1112
|
-
|
|
1113
|
-
await ctx.options.sessions.set(id, ctx.session);
|
|
1114
|
-
loaded.set(ctx, { id, data: JSON.stringify(ctx.session) });
|
|
1113
|
+
await settings.sessions.set(id, auth2);
|
|
1115
1114
|
if (strategy.includes("token")) {
|
|
1116
1115
|
const exposed = await onUser(user, ctx);
|
|
1117
1116
|
assertUser(exposed, "onUser");
|
|
@@ -1631,6 +1630,7 @@ function parseAuthOptions(auth2) {
|
|
|
1631
1630
|
const onUser = auth2.onUser || defaultOnUser;
|
|
1632
1631
|
const onToken = auth2.onToken || defaultOnUser;
|
|
1633
1632
|
const users = auth2.users ? toStore(auth2.users) : null;
|
|
1633
|
+
const sessions = auth2.sessions ? toStoreExpiring(auth2.sessions, "1w") : null;
|
|
1634
1634
|
return {
|
|
1635
1635
|
strategy,
|
|
1636
1636
|
providers: list,
|
|
@@ -1640,7 +1640,8 @@ function parseAuthOptions(auth2) {
|
|
|
1640
1640
|
onUser,
|
|
1641
1641
|
onToken,
|
|
1642
1642
|
onLogout,
|
|
1643
|
-
users
|
|
1643
|
+
users,
|
|
1644
|
+
sessions
|
|
1644
1645
|
};
|
|
1645
1646
|
}
|
|
1646
1647
|
|
|
@@ -1830,7 +1831,8 @@ function config(options = {}) {
|
|
|
1830
1831
|
const level = raw === true ? "info" : raw === false ? void 0 : raw;
|
|
1831
1832
|
const log = createLogger(level);
|
|
1832
1833
|
const settings = {
|
|
1833
|
-
|
|
1834
|
+
// `env.PORT` is a string, so coerce it: `settings.port` is a number
|
|
1835
|
+
port: options.port || Number(env2.PORT) || 3e3,
|
|
1834
1836
|
secret: options.secret || env2.SECRET || `unsafe-${createId()}`,
|
|
1835
1837
|
log,
|
|
1836
1838
|
// How request bodies are read: parsed into ctx.body by default; `raw` keeps
|
|
@@ -1838,11 +1840,7 @@ function config(options = {}) {
|
|
|
1838
1840
|
parser: options.parser ?? "parse",
|
|
1839
1841
|
// Secure-by-default response headers + trustProxy for ctx.ip. `false` turns
|
|
1840
1842
|
// the added headers off; see resolveSecurity for the defaults.
|
|
1841
|
-
security: resolveSecurity(options.security)
|
|
1842
|
-
// Sessions: one record per device, exposed as ctx.session. Anything
|
|
1843
|
-
// polystore accepts works; raw sources (a Map, a Redis client) get a 1w
|
|
1844
|
-
// expiry, a built store is honored as-is, prefix and expiry included.
|
|
1845
|
-
sessions: toStoreExpiring(options.sessions ?? /* @__PURE__ */ new Map(), "1w")
|
|
1843
|
+
security: resolveSecurity(options.security)
|
|
1846
1844
|
};
|
|
1847
1845
|
if (options.cache !== void 0) settings.cache = options.cache;
|
|
1848
1846
|
options.cors = options.cors || env2.CORS || null;
|
|
@@ -1885,10 +1883,10 @@ function config(options = {}) {
|
|
|
1885
1883
|
settings.public = publicDir ? bucket(publicDir) : null;
|
|
1886
1884
|
settings.uploads = resolveUploads(options.uploads);
|
|
1887
1885
|
const production = env2.NODE_ENV === "production";
|
|
1888
|
-
const defaulted = options.sessions == null;
|
|
1889
|
-
settings.sessionsDefault = defaulted;
|
|
1890
1886
|
if (options.auth || env2.AUTH) {
|
|
1891
|
-
settings.auth = parseAuthOptions(
|
|
1887
|
+
settings.auth = parseAuthOptions(
|
|
1888
|
+
options.auth || env2.AUTH || null
|
|
1889
|
+
);
|
|
1892
1890
|
}
|
|
1893
1891
|
if (settings.auth) {
|
|
1894
1892
|
if (!settings.auth.users) {
|
|
@@ -1899,10 +1897,13 @@ function config(options = {}) {
|
|
|
1899
1897
|
}
|
|
1900
1898
|
settings.auth.users = toStore(/* @__PURE__ */ new Map());
|
|
1901
1899
|
}
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1900
|
+
if (!settings.auth.sessions && !settings.auth.strategy.includes("jwt")) {
|
|
1901
|
+
if (production) {
|
|
1902
|
+
throw new Error(
|
|
1903
|
+
"Auth in production needs a persistent `sessions` store, like auth: { ..., sessions: kv(redis).prefix('session:') }."
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
settings.auth.sessions = toStoreExpiring(/* @__PURE__ */ new Map(), "1w");
|
|
1906
1907
|
}
|
|
1907
1908
|
}
|
|
1908
1909
|
if (settings.auth?.strategy.includes("jwt") && settings.secret.startsWith("unsafe-")) {
|
|
@@ -1928,7 +1929,6 @@ function config(options = {}) {
|
|
|
1928
1929
|
}
|
|
1929
1930
|
if (settings.public) log.message("public", loc(options.public));
|
|
1930
1931
|
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
1931
|
-
if (options.sessions) log.message("sessions", "enabled");
|
|
1932
1932
|
if (settings.cors) {
|
|
1933
1933
|
const origin = settings.cors.origin === true ? "*" : String(settings.cors.origin);
|
|
1934
1934
|
log.message("cors", origin);
|
|
@@ -2038,74 +2038,17 @@ function getMachine() {
|
|
|
2038
2038
|
}
|
|
2039
2039
|
|
|
2040
2040
|
// src/parseResponse.ts
|
|
2041
|
-
var warned = false;
|
|
2042
|
-
var warnDefault = () => {
|
|
2043
|
-
if (warned) return;
|
|
2044
|
-
warned = true;
|
|
2045
|
-
console.warn(
|
|
2046
|
-
"[server:sessions] Using the default in-memory session store in production: sessions are lost on restart and not shared across instances. Configure one with sessions: kv(redis).prefix('session:')."
|
|
2047
|
-
);
|
|
2048
|
-
};
|
|
2049
2041
|
async function parseResponse(out, ctx) {
|
|
2050
2042
|
if (!out && typeof out !== "string") return null;
|
|
2051
2043
|
if (typeof out === "function") {
|
|
2052
2044
|
out = await out(ctx);
|
|
2053
|
-
|
|
2054
|
-
if (out && typeof out.send === "function" && out.res?.headers instanceof Headers) {
|
|
2055
|
-
out = out.send();
|
|
2056
|
-
}
|
|
2057
|
-
if (out instanceof Blob) {
|
|
2058
|
-
out = new Response(out, { headers: { "Content-Type": out.type } });
|
|
2059
|
-
}
|
|
2060
|
-
if (out && typeof out.stream === "function" && typeof out.bytes === "function" && typeof out.exists === "function" && typeof out.name === "string") {
|
|
2061
|
-
if (!await out.exists()) {
|
|
2062
|
-
out = new Response(null, { status: 404 });
|
|
2063
|
-
} else {
|
|
2064
|
-
const type2 = fileType(out);
|
|
2065
|
-
out = new Response(
|
|
2066
|
-
out.stream(),
|
|
2067
|
-
type2 ? { headers: { "content-type": type2 } } : void 0
|
|
2068
|
-
);
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
if (out instanceof ReadableStream) {
|
|
2072
|
-
out = new Response(out);
|
|
2073
|
-
}
|
|
2074
|
-
if (out instanceof Uint8Array) {
|
|
2075
|
-
out = new Response(out);
|
|
2045
|
+
if (!out && typeof out !== "string") return null;
|
|
2076
2046
|
}
|
|
2077
2047
|
if (typeof out === "number") {
|
|
2078
|
-
out = new Response(
|
|
2079
|
-
}
|
|
2080
|
-
if (typeof out === "string") {
|
|
2081
|
-
const type2 = isHtml(out) ? mimes_default.html : mimes_default.text;
|
|
2082
|
-
out = new Response(out, {
|
|
2083
|
-
headers: {
|
|
2084
|
-
"content-type": type2,
|
|
2085
|
-
"content-length": String(Buffer.byteLength(out))
|
|
2086
|
-
}
|
|
2087
|
-
});
|
|
2088
|
-
}
|
|
2089
|
-
if (out?.constructor === Object || Array.isArray(out)) {
|
|
2090
|
-
out = json(out);
|
|
2091
|
-
}
|
|
2092
|
-
if (out[Symbol.iterator]) {
|
|
2093
|
-
out = new Response(iteratorToReadable(out));
|
|
2048
|
+
out = new Response(null, { status: out });
|
|
2094
2049
|
}
|
|
2095
|
-
if (
|
|
2096
|
-
out =
|
|
2097
|
-
}
|
|
2098
|
-
if (out instanceof Response && out.url && out.body) {
|
|
2099
|
-
out = new Response(out.body, {
|
|
2100
|
-
status: out.status,
|
|
2101
|
-
headers: out.headers
|
|
2102
|
-
});
|
|
2103
|
-
if (/^(br|gzip)$/.test(out.headers.get("content-encoding") || "")) {
|
|
2104
|
-
out.headers.delete("content-encoding");
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
if (!(out instanceof Response)) {
|
|
2108
|
-
throw new Error(`Invalid response type ${out}`);
|
|
2050
|
+
if (!(out instanceof Response) || out.url) {
|
|
2051
|
+
out = await send(out);
|
|
2109
2052
|
}
|
|
2110
2053
|
applyCors(out, ctx);
|
|
2111
2054
|
applySecurity(out, ctx);
|
|
@@ -2113,27 +2056,6 @@ async function parseResponse(out, ctx) {
|
|
|
2113
2056
|
if (ctx.time?.times?.length > 1) {
|
|
2114
2057
|
out.headers.set("Server-Timing", ctx.time.headers());
|
|
2115
2058
|
}
|
|
2116
|
-
const prev = loaded.get(ctx);
|
|
2117
|
-
if (prev && JSON.stringify(ctx.session ?? {}) !== prev.data) {
|
|
2118
|
-
if (ctx.options.sessionsDefault && ctx.platform.production) {
|
|
2119
|
-
warnDefault();
|
|
2120
|
-
}
|
|
2121
|
-
let id = prev.id;
|
|
2122
|
-
if (!id) {
|
|
2123
|
-
id = createId();
|
|
2124
|
-
out.headers.append(
|
|
2125
|
-
"set-cookie",
|
|
2126
|
-
createCookies("session", {
|
|
2127
|
-
value: id,
|
|
2128
|
-
path: "/",
|
|
2129
|
-
httpOnly: true,
|
|
2130
|
-
secure: ctx.platform.production,
|
|
2131
|
-
sameSite: "Lax"
|
|
2132
|
-
})
|
|
2133
|
-
);
|
|
2134
|
-
}
|
|
2135
|
-
ctx.options.sessions.set(id, ctx.session);
|
|
2136
|
-
}
|
|
2137
2059
|
return out;
|
|
2138
2060
|
}
|
|
2139
2061
|
|
|
@@ -2294,23 +2216,30 @@ import * as crypto2 from "crypto";
|
|
|
2294
2216
|
import { getRandomValues } from "crypto";
|
|
2295
2217
|
import { promisify } from "util";
|
|
2296
2218
|
async function hash2(password) {
|
|
2297
|
-
if ("
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
parallelism: 4,
|
|
2303
|
-
tagLength: 64,
|
|
2304
|
-
memory: 65536,
|
|
2305
|
-
passes: 3
|
|
2219
|
+
if ("Bun" in globalThis) {
|
|
2220
|
+
return await Bun.password.hash(password, {
|
|
2221
|
+
algorithm: "argon2id",
|
|
2222
|
+
memoryCost: 65536,
|
|
2223
|
+
timeCost: 3
|
|
2306
2224
|
});
|
|
2307
|
-
return buf.toString("base64");
|
|
2308
2225
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2226
|
+
if (!("argon2" in crypto2)) {
|
|
2227
|
+
throw new Error(
|
|
2228
|
+
"Password hashing needs argon2: run on Bun, or on Node 24+ where node:crypto provides it."
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
const nonce = getRandomValues(new Uint8Array(32));
|
|
2232
|
+
const argon23 = promisify(crypto2.argon2);
|
|
2233
|
+
const buf = await argon23("argon2id", {
|
|
2234
|
+
message: Buffer.from(password),
|
|
2235
|
+
nonce,
|
|
2236
|
+
parallelism: 1,
|
|
2237
|
+
tagLength: 32,
|
|
2238
|
+
memory: 65536,
|
|
2239
|
+
passes: 3
|
|
2313
2240
|
});
|
|
2241
|
+
const b64 = (bytes) => Buffer.from(bytes).toString("base64").replace(/=+$/, "");
|
|
2242
|
+
return `$argon2id$v=19$m=65536,t=3,p=1$${b64(nonce)}$${b64(buf)}`;
|
|
2314
2243
|
}
|
|
2315
2244
|
|
|
2316
2245
|
// src/helpers/iteratorAsyncToReadable.ts
|
|
@@ -2405,16 +2334,6 @@ function toWeb(nodeStream) {
|
|
|
2405
2334
|
|
|
2406
2335
|
// src/helpers/verify.ts
|
|
2407
2336
|
import * as crypto3 from "crypto";
|
|
2408
|
-
function timingSafeEqual(a, b) {
|
|
2409
|
-
const len = Math.max(a.length, b.length);
|
|
2410
|
-
let mismatch = a.length ^ b.length;
|
|
2411
|
-
for (let i = 0; i < len; i++) {
|
|
2412
|
-
const ca = a.charCodeAt(i) || 0;
|
|
2413
|
-
const cb = b.charCodeAt(i) || 0;
|
|
2414
|
-
mismatch |= ca ^ cb;
|
|
2415
|
-
}
|
|
2416
|
-
return mismatch === 0;
|
|
2417
|
-
}
|
|
2418
2337
|
async function verify2(password, hash3) {
|
|
2419
2338
|
if ("Bun" in globalThis) {
|
|
2420
2339
|
return Bun.password.verify(password, hash3, "argon2id");
|
|
@@ -2439,11 +2358,8 @@ async function verify2(password, hash3) {
|
|
|
2439
2358
|
},
|
|
2440
2359
|
(err, derivedKey) => {
|
|
2441
2360
|
if (err) return reject(err);
|
|
2442
|
-
if (derivedKey.length
|
|
2443
|
-
|
|
2444
|
-
} else {
|
|
2445
|
-
resolve(false);
|
|
2446
|
-
}
|
|
2361
|
+
if (derivedKey.length !== expected.length) return resolve(false);
|
|
2362
|
+
resolve(crypto3.timingSafeEqual(derivedKey, expected));
|
|
2447
2363
|
}
|
|
2448
2364
|
);
|
|
2449
2365
|
});
|
|
@@ -2472,14 +2388,10 @@ async function getJwtUser(ctx) {
|
|
|
2472
2388
|
return exposed;
|
|
2473
2389
|
}
|
|
2474
2390
|
async function getAuthSession(ctx) {
|
|
2475
|
-
if (loaded.has(ctx)) {
|
|
2476
|
-
const session3 = ctx.session;
|
|
2477
|
-
return session3?.user ? session3 : void 0;
|
|
2478
|
-
}
|
|
2479
2391
|
const id = findSessionId(ctx);
|
|
2480
2392
|
if (!id) return;
|
|
2481
|
-
const
|
|
2482
|
-
return
|
|
2393
|
+
const session = await ctx.options.auth.sessions.get(id);
|
|
2394
|
+
return session?.user ? session : void 0;
|
|
2483
2395
|
}
|
|
2484
2396
|
async function getUser(ctx) {
|
|
2485
2397
|
if (!ctx.options.auth) return;
|
|
@@ -2504,10 +2416,8 @@ async function getUser(ctx) {
|
|
|
2504
2416
|
async function logout(ctx) {
|
|
2505
2417
|
const { strategy } = ctx.options.auth;
|
|
2506
2418
|
if (!strategy.includes("jwt")) {
|
|
2507
|
-
const prev =
|
|
2508
|
-
if (prev
|
|
2509
|
-
ctx.session = {};
|
|
2510
|
-
loaded.set(ctx, { id: void 0, data: "{}" });
|
|
2419
|
+
const prev = findSessionId(ctx);
|
|
2420
|
+
if (prev) await ctx.options.auth.sessions.del(prev);
|
|
2511
2421
|
}
|
|
2512
2422
|
if (ctx.options.auth.onLogout) await ctx.options.auth.onLogout(ctx);
|
|
2513
2423
|
if (strategy.includes("token") || strategy.includes("jwt")) {
|
|
@@ -3034,7 +2944,6 @@ async function createNode(req, app, signal = new AbortController().signal) {
|
|
|
3034
2944
|
headers: headers2,
|
|
3035
2945
|
cookies: cookies2,
|
|
3036
2946
|
signal,
|
|
3037
|
-
session: {},
|
|
3038
2947
|
init,
|
|
3039
2948
|
app,
|
|
3040
2949
|
ip: clientIp(headers2, {
|
|
@@ -3075,7 +2984,6 @@ async function createWinter(req, app, server2) {
|
|
|
3075
2984
|
headers: headers2,
|
|
3076
2985
|
cookies: cookies2,
|
|
3077
2986
|
signal: req.signal,
|
|
3078
|
-
session: {},
|
|
3079
2987
|
init,
|
|
3080
2988
|
app,
|
|
3081
2989
|
ip: clientIp(headers2, {
|
|
@@ -3326,7 +3234,6 @@ var Server = class extends Router {
|
|
|
3326
3234
|
app.use(timer);
|
|
3327
3235
|
if (this.settings.cors) app.use(preflight);
|
|
3328
3236
|
app.use(assets);
|
|
3329
|
-
app.use(session);
|
|
3330
3237
|
if (this.settings.auth) {
|
|
3331
3238
|
auth(app);
|
|
3332
3239
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
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",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"types": "index.d.ts",
|
|
26
26
|
"files": [
|
|
27
27
|
"index.d.ts",
|
|
28
|
-
"src/jsx
|
|
28
|
+
"src/jsx/*.js",
|
|
29
|
+
"src/jsx/jsx-runtime.d.ts"
|
|
29
30
|
],
|
|
30
31
|
"exports": {
|
|
31
32
|
".": "./index.js",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"menu": {
|
|
46
47
|
"Documentation": "/documentation",
|
|
47
48
|
"Tutorials": "/tutorials",
|
|
49
|
+
"Donate": "https://www.paypal.com/paypalme/franciscopresencia/19",
|
|
48
50
|
"Github": "https://github.com/franciscop/server-next"
|
|
49
51
|
},
|
|
50
52
|
"documentation": {
|
package/readme.md
CHANGED
|
@@ -15,16 +15,24 @@ export default server({ uploads: './uploads' })
|
|
|
15
15
|
.post('/avatar', (ctx) => ctx.body.avatar.path);
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Key-value stores and file storage come included, so
|
|
18
|
+
Key-value stores and file storage come included, so logins work out of the box and `uploads` takes a folder path. For Redis, S3 and the rest, pass the client straight in:
|
|
19
19
|
|
|
20
20
|
```js
|
|
21
|
-
import server, { bucket } from '@server/next';
|
|
21
|
+
import server, { bucket, kv } from '@server/next';
|
|
22
22
|
import { createClient } from 'redis';
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const redis = kv(createClient({ url }));
|
|
25
25
|
const uploads = bucket.S3('my-bucket', { id, key });
|
|
26
26
|
|
|
27
|
-
export default server({
|
|
27
|
+
export default server({
|
|
28
|
+
uploads,
|
|
29
|
+
auth: {
|
|
30
|
+
strategy: 'cookie',
|
|
31
|
+
providers: ['github'],
|
|
32
|
+
users: redis.prefix('user:'),
|
|
33
|
+
sessions: redis.prefix('session:'),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
See the [full documentation](https://serverjs.io/documentation).
|