@server/next 0.32.0 → 0.34.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 +16 -12
- package/index.js +64 -58
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -104,17 +104,16 @@ type AuthUser<T = Record<string, any>> = T & {
|
|
|
104
104
|
strategy: Strategy;
|
|
105
105
|
email: string;
|
|
106
106
|
};
|
|
107
|
-
type
|
|
108
|
-
type AuthOption = `${Strategy}:${Provider | ProviderString}` | {
|
|
109
|
-
provider: Provider | ProviderString | Provider[];
|
|
107
|
+
type AuthOption = `${Strategy}:${Provider}` | {
|
|
110
108
|
strategy: Strategy;
|
|
109
|
+
providers: Provider | Provider[];
|
|
111
110
|
session?: KVStore;
|
|
112
111
|
store?: KVStore;
|
|
113
112
|
redirect?: string;
|
|
114
113
|
cleanUser?: <T = AuthUser>(user: T) => T | Promise<T>;
|
|
115
114
|
};
|
|
116
115
|
type AuthSettings = {
|
|
117
|
-
|
|
116
|
+
providers: Provider[];
|
|
118
117
|
strategy: Strategy;
|
|
119
118
|
store: KVStore;
|
|
120
119
|
session: KVStore;
|
|
@@ -130,9 +129,20 @@ type Logger = {
|
|
|
130
129
|
};
|
|
131
130
|
type SecurityOptions = {
|
|
132
131
|
trustProxy?: boolean;
|
|
132
|
+
frameguard?: boolean | string;
|
|
133
|
+
noSniff?: boolean;
|
|
134
|
+
referrerPolicy?: boolean | string;
|
|
135
|
+
hsts?: boolean | string;
|
|
136
|
+
xssProtection?: boolean;
|
|
137
|
+
csp?: boolean | string;
|
|
138
|
+
coop?: boolean | string;
|
|
139
|
+
corp?: boolean | string;
|
|
140
|
+
permissionsPolicy?: string;
|
|
133
141
|
};
|
|
134
142
|
type SecuritySettings = {
|
|
135
143
|
trustProxy: boolean;
|
|
144
|
+
headers: Record<string, string>;
|
|
145
|
+
hsts: string | null;
|
|
136
146
|
};
|
|
137
147
|
type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
|
|
138
148
|
type Options = {
|
|
@@ -151,7 +161,7 @@ type Options = {
|
|
|
151
161
|
onError?: OnError;
|
|
152
162
|
log?: LogLevel | boolean;
|
|
153
163
|
favicon?: string | Bucket;
|
|
154
|
-
security?: SecurityOptions;
|
|
164
|
+
security?: boolean | SecurityOptions;
|
|
155
165
|
body?: BodyMode;
|
|
156
166
|
};
|
|
157
167
|
type Settings = {
|
|
@@ -197,11 +207,6 @@ type PathToParams<Path extends string> = ParamsToObject<ExtractPathParams<Path>>
|
|
|
197
207
|
type BunEnv = Record<string, string> & {
|
|
198
208
|
upgrade?: (req: Request) => boolean;
|
|
199
209
|
};
|
|
200
|
-
type EventCallback = (data: Context & SerializableValue) => void;
|
|
201
|
-
type Events = Record<string, EventCallback[]> & {
|
|
202
|
-
on?: (key: string, cb: (value?: Context & SerializableValue) => void) => void;
|
|
203
|
-
trigger?: (key: string, value?: Partial<Context & SerializableValue>) => void;
|
|
204
|
-
};
|
|
205
210
|
type Context<Params extends Record<string, string | undefined> = Record<string, string>, O extends ServerConfig = object> = {
|
|
206
211
|
method: Method;
|
|
207
212
|
ip: string;
|
|
@@ -224,7 +229,6 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
|
|
|
224
229
|
User?: infer U;
|
|
225
230
|
} ? U extends Record<"User", infer Inner> ? Inner : AuthUser : AuthUser;
|
|
226
231
|
init: number;
|
|
227
|
-
events: Events;
|
|
228
232
|
req?: Request;
|
|
229
233
|
res?: Response & {
|
|
230
234
|
cookies?: Record<string, string>;
|
|
@@ -458,4 +462,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
|
|
|
458
462
|
}
|
|
459
463
|
declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
|
|
460
464
|
|
|
461
|
-
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type
|
|
465
|
+
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
|
package/index.js
CHANGED
|
@@ -54,7 +54,7 @@ ServerError_default.extend({
|
|
|
54
54
|
message: "Invalid Authorization type '{strategy}', valid one is '{valid}'"
|
|
55
55
|
},
|
|
56
56
|
AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
|
|
57
|
-
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.
|
|
57
|
+
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.providers'",
|
|
58
58
|
AUTH_INVALID_PROVIDER: {
|
|
59
59
|
status: 401,
|
|
60
60
|
message: "Invalid provider '{provider}', valid ones are: '{valid}'"
|
|
@@ -1113,37 +1113,27 @@ function defaultCleanUser(fullUser) {
|
|
|
1113
1113
|
const { password: _password, ...user } = fullUser;
|
|
1114
1114
|
return user;
|
|
1115
1115
|
}
|
|
1116
|
-
var
|
|
1117
|
-
function getProviders(provider) {
|
|
1118
|
-
if (typeof provider === "string") {
|
|
1119
|
-
provider = provider.split("|");
|
|
1120
|
-
}
|
|
1121
|
-
const invalidProvider = provider.find((p) => !providersKeys.includes(p));
|
|
1122
|
-
if (invalidProvider) {
|
|
1123
|
-
throw new Error(
|
|
1124
|
-
`Provider "${invalidProvider}" not found, available ones are "${providersKeys.join('", "')}"`
|
|
1125
|
-
);
|
|
1126
|
-
}
|
|
1127
|
-
return provider;
|
|
1128
|
-
}
|
|
1116
|
+
var available = Object.keys(providers_default);
|
|
1129
1117
|
function parseAuthOptions(auth2, all) {
|
|
1130
1118
|
if (!auth2) return null;
|
|
1131
1119
|
if (typeof auth2 === "string") {
|
|
1132
|
-
const [strategy2,
|
|
1133
|
-
|
|
1134
|
-
auth2 = { strategy: strategy2, provider: provider2 };
|
|
1135
|
-
}
|
|
1136
|
-
if (!auth2.strategy) {
|
|
1137
|
-
throw new Error("Auth options needs a strategy");
|
|
1120
|
+
const [strategy2, provider] = auth2.split(":");
|
|
1121
|
+
auth2 = { strategy: strategy2, providers: provider ? [provider] : [] };
|
|
1138
1122
|
}
|
|
1139
|
-
if (!auth2.strategy
|
|
1123
|
+
if (!auth2.strategy?.length) {
|
|
1140
1124
|
throw new Error("Auth options needs a strategy");
|
|
1141
1125
|
}
|
|
1142
1126
|
const strategy = auth2.strategy;
|
|
1143
|
-
|
|
1127
|
+
const list = Array.isArray(auth2.providers) ? auth2.providers : auth2.providers ? [auth2.providers] : [];
|
|
1128
|
+
if (!list.length) {
|
|
1144
1129
|
throw new Error("Auth options needs a provider");
|
|
1145
1130
|
}
|
|
1146
|
-
const
|
|
1131
|
+
const invalid = list.find((p) => !available.includes(p));
|
|
1132
|
+
if (invalid) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`Provider "${invalid}" not found, available ones are "${available.join('", "')}"`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1147
1137
|
const redirect2 = auth2.redirect || defaultRedirect;
|
|
1148
1138
|
const cleanUser = auth2.cleanUser || defaultCleanUser;
|
|
1149
1139
|
if (!auth2.store && !all.store) {
|
|
@@ -1155,13 +1145,10 @@ function parseAuthOptions(auth2, all) {
|
|
|
1155
1145
|
const store = auth2.store || all.store.prefix("user:");
|
|
1156
1146
|
const session2 = auth2.session || all.store.prefix("auth:");
|
|
1157
1147
|
return {
|
|
1158
|
-
// Base main configuration
|
|
1159
1148
|
strategy,
|
|
1160
|
-
|
|
1161
|
-
// Extra configuration
|
|
1149
|
+
providers: list,
|
|
1162
1150
|
redirect: redirect2,
|
|
1163
1151
|
cleanUser,
|
|
1164
|
-
// Stores for the auth session and users
|
|
1165
1152
|
store,
|
|
1166
1153
|
session: session2
|
|
1167
1154
|
};
|
|
@@ -1371,6 +1358,47 @@ function createLogger(level) {
|
|
|
1371
1358
|
};
|
|
1372
1359
|
}
|
|
1373
1360
|
|
|
1361
|
+
// src/helpers/security.ts
|
|
1362
|
+
function resolveSecurity(security) {
|
|
1363
|
+
const off = security === false;
|
|
1364
|
+
const o = security && typeof security === "object" ? security : {};
|
|
1365
|
+
const val = (v, def) => v === false ? null : v === true || v == null ? def : v;
|
|
1366
|
+
const map2 = off ? {} : {
|
|
1367
|
+
"x-frame-options": val(o.frameguard, "SAMEORIGIN"),
|
|
1368
|
+
"x-content-type-options": o.noSniff === false ? null : "nosniff",
|
|
1369
|
+
"referrer-policy": val(
|
|
1370
|
+
o.referrerPolicy,
|
|
1371
|
+
"strict-origin-when-cross-origin"
|
|
1372
|
+
),
|
|
1373
|
+
"x-xss-protection": o.xssProtection === false ? null : "0",
|
|
1374
|
+
// Opt-in: default off
|
|
1375
|
+
"content-security-policy": val(o.csp, null),
|
|
1376
|
+
"cross-origin-opener-policy": val(o.coop, null),
|
|
1377
|
+
"cross-origin-resource-policy": val(o.corp, null),
|
|
1378
|
+
"permissions-policy": o.permissionsPolicy ?? null
|
|
1379
|
+
};
|
|
1380
|
+
const headers2 = {};
|
|
1381
|
+
for (const key in map2) {
|
|
1382
|
+
const value = map2[key];
|
|
1383
|
+
if (value) headers2[key] = value;
|
|
1384
|
+
}
|
|
1385
|
+
return {
|
|
1386
|
+
trustProxy: o.trustProxy ?? true,
|
|
1387
|
+
headers: headers2,
|
|
1388
|
+
hsts: off ? null : val(o.hsts, "max-age=15552000; includeSubDomains")
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function applySecurity(res, ctx) {
|
|
1392
|
+
const security = ctx.options.security;
|
|
1393
|
+
if (!security) return;
|
|
1394
|
+
for (const key in security.headers) {
|
|
1395
|
+
if (!res.headers.has(key)) res.headers.set(key, security.headers[key]);
|
|
1396
|
+
}
|
|
1397
|
+
if (security.hsts && ctx.platform.production && !res.headers.has("strict-transport-security")) {
|
|
1398
|
+
res.headers.set("strict-transport-security", security.hsts);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1374
1402
|
// src/helpers/config.ts
|
|
1375
1403
|
function config(options = {}) {
|
|
1376
1404
|
const env2 = globalThis.env;
|
|
@@ -1384,11 +1412,9 @@ function config(options = {}) {
|
|
|
1384
1412
|
// How request bodies are read: parsed into ctx.body by default; `raw` keeps
|
|
1385
1413
|
// the Buffer, `stream` hands the handler the unread web ReadableStream.
|
|
1386
1414
|
body: options.body ?? "parse",
|
|
1387
|
-
//
|
|
1388
|
-
//
|
|
1389
|
-
security:
|
|
1390
|
-
trustProxy: options.security?.trustProxy ?? true
|
|
1391
|
-
}
|
|
1415
|
+
// Secure-by-default response headers + trustProxy for ctx.ip. `false` turns
|
|
1416
|
+
// the added headers off; see resolveSecurity for the defaults.
|
|
1417
|
+
security: resolveSecurity(options.security)
|
|
1392
1418
|
};
|
|
1393
1419
|
options.cors = options.cors || env2.CORS || null;
|
|
1394
1420
|
if (options.cors) {
|
|
@@ -1452,7 +1478,7 @@ function config(options = {}) {
|
|
|
1452
1478
|
});
|
|
1453
1479
|
const loc = (v) => typeof v === "string" ? v : "enabled";
|
|
1454
1480
|
if (settings.auth) {
|
|
1455
|
-
log.message("auth", `${settings.auth.
|
|
1481
|
+
log.message("auth", `${settings.auth.providers.join(", ")} auth enabled`);
|
|
1456
1482
|
}
|
|
1457
1483
|
if (settings.public) log.message("public", loc(options.public));
|
|
1458
1484
|
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
@@ -1613,6 +1639,7 @@ async function parseResponse(out, ctx) {
|
|
|
1613
1639
|
throw new Error(`Invalid response type ${out}`);
|
|
1614
1640
|
}
|
|
1615
1641
|
applyCors(out, ctx);
|
|
1642
|
+
applySecurity(out, ctx);
|
|
1616
1643
|
if (ctx.time?.times?.length > 1) {
|
|
1617
1644
|
out.headers.set("Server-Timing", ctx.time.headers());
|
|
1618
1645
|
}
|
|
@@ -1769,6 +1796,7 @@ async function getResponse(app, ctx) {
|
|
|
1769
1796
|
} catch (error) {
|
|
1770
1797
|
const res = await ctx.options.onError(error, ctx);
|
|
1771
1798
|
applyCors(res, ctx);
|
|
1799
|
+
applySecurity(res, ctx);
|
|
1772
1800
|
return res;
|
|
1773
1801
|
}
|
|
1774
1802
|
}
|
|
@@ -2062,10 +2090,10 @@ async function getUser(ctx) {
|
|
|
2062
2090
|
valid: options.strategy
|
|
2063
2091
|
});
|
|
2064
2092
|
}
|
|
2065
|
-
if (!options.
|
|
2093
|
+
if (!options.providers.includes(auth2.provider)) {
|
|
2066
2094
|
throw ServerError_default.AUTH_INVALID_PROVIDER({
|
|
2067
2095
|
provider: auth2.provider,
|
|
2068
|
-
valid: options.
|
|
2096
|
+
valid: options.providers
|
|
2069
2097
|
});
|
|
2070
2098
|
}
|
|
2071
2099
|
const user = await ctx.options.auth.store.get(auth2.user);
|
|
@@ -2109,7 +2137,7 @@ function auth(app) {
|
|
|
2109
2137
|
ctx.user = await getUser(ctx);
|
|
2110
2138
|
});
|
|
2111
2139
|
app.post("/auth/logout", logout);
|
|
2112
|
-
const enabled = app.settings.auth.
|
|
2140
|
+
const enabled = app.settings.auth.providers;
|
|
2113
2141
|
for (const name of oauth2) {
|
|
2114
2142
|
if (!enabled.includes(name)) continue;
|
|
2115
2143
|
const key = name.toUpperCase();
|
|
@@ -2384,22 +2412,6 @@ function timer(ctx) {
|
|
|
2384
2412
|
// src/context/node.ts
|
|
2385
2413
|
import { TLSSocket } from "tls";
|
|
2386
2414
|
|
|
2387
|
-
// src/context/createEvents.ts
|
|
2388
|
-
function createEvents() {
|
|
2389
|
-
const events = {};
|
|
2390
|
-
events.on = (name, callback3) => {
|
|
2391
|
-
events[name] = events[name] || [];
|
|
2392
|
-
events[name].push(callback3);
|
|
2393
|
-
};
|
|
2394
|
-
events.trigger = (name, data) => {
|
|
2395
|
-
if (!events[name]) return;
|
|
2396
|
-
for (const cb of events[name]) {
|
|
2397
|
-
cb(data);
|
|
2398
|
-
}
|
|
2399
|
-
};
|
|
2400
|
-
return events;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
2415
|
// src/context/isValidMethod.ts
|
|
2404
2416
|
var methods = [
|
|
2405
2417
|
"get",
|
|
@@ -2443,7 +2455,6 @@ async function createNode(req, app) {
|
|
|
2443
2455
|
}),
|
|
2444
2456
|
getStream: () => toWeb(req)
|
|
2445
2457
|
};
|
|
2446
|
-
const events = createEvents();
|
|
2447
2458
|
const ctx = {
|
|
2448
2459
|
options: app.settings,
|
|
2449
2460
|
platform: app.platform,
|
|
@@ -2454,7 +2465,6 @@ async function createNode(req, app) {
|
|
|
2454
2465
|
cookies: cookies2,
|
|
2455
2466
|
session: {},
|
|
2456
2467
|
init,
|
|
2457
|
-
events,
|
|
2458
2468
|
app,
|
|
2459
2469
|
ip: clientIp(headers2, {
|
|
2460
2470
|
remoteAddress: req.socket.remoteAddress || "",
|
|
@@ -2485,7 +2495,6 @@ async function createWinter(req, app, server2) {
|
|
|
2485
2495
|
getBuffer: async () => Buffer.from(await req.arrayBuffer()),
|
|
2486
2496
|
getStream: () => req.body ?? void 0
|
|
2487
2497
|
};
|
|
2488
|
-
const events = createEvents();
|
|
2489
2498
|
const ctx = {
|
|
2490
2499
|
options: app.settings,
|
|
2491
2500
|
platform: app.platform,
|
|
@@ -2496,7 +2505,6 @@ async function createWinter(req, app, server2) {
|
|
|
2496
2505
|
cookies: cookies2,
|
|
2497
2506
|
session: {},
|
|
2498
2507
|
init,
|
|
2499
|
-
events,
|
|
2500
2508
|
app,
|
|
2501
2509
|
ip: clientIp(headers2, {
|
|
2502
2510
|
remoteAddress: server2?.requestIP?.(req)?.address || "",
|
|
@@ -2513,7 +2521,6 @@ var Winter = async (app, request, env2) => {
|
|
|
2513
2521
|
Object.assign(globalThis.env, env2);
|
|
2514
2522
|
const ctx = await createWinter(request, app, env2);
|
|
2515
2523
|
const res = await handleRequest(app, ctx);
|
|
2516
|
-
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
2517
2524
|
return res;
|
|
2518
2525
|
};
|
|
2519
2526
|
var Node = async (app) => {
|
|
@@ -2545,7 +2552,6 @@ var Netlify = async (app, request, context) => {
|
|
|
2545
2552
|
}
|
|
2546
2553
|
const ctx = await createWinter(request, app);
|
|
2547
2554
|
const res = await handleRequest(app, ctx);
|
|
2548
|
-
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
2549
2555
|
return res;
|
|
2550
2556
|
};
|
|
2551
2557
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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",
|