@server/next 0.47.2 → 0.48.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 +4 -9
- package/index.js +90 -91
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -203,7 +203,7 @@ type RedirectOption = string | ((user: any, ctx: Context) => Awaitable<string>)
|
|
|
203
203
|
};
|
|
204
204
|
type AuthConfig<U = AuthProfile> = {
|
|
205
205
|
providers: string | readonly string[] | Record<string, string | ProviderOptions>;
|
|
206
|
-
strategy?: Strategy
|
|
206
|
+
strategy?: Strategy;
|
|
207
207
|
expires?: string;
|
|
208
208
|
redirect?: RedirectOption;
|
|
209
209
|
onLogin?: (profile: AuthProfile, ctx: Context) => Awaitable<string | number | undefined>;
|
|
@@ -224,8 +224,8 @@ type AuthInstance = {
|
|
|
224
224
|
user?: (ctx: Context) => Awaitable<any>;
|
|
225
225
|
};
|
|
226
226
|
type AuthFunction<U = any> = (ctx: Context<any>) => Awaitable<U>;
|
|
227
|
-
type AuthOption = string | AuthFunction | AuthConfig<any> | AuthVerify<any> | AuthInstance
|
|
228
|
-
type UserOf<A> = A extends
|
|
227
|
+
type AuthOption = string | AuthFunction | AuthConfig<any> | AuthVerify<any> | AuthInstance;
|
|
228
|
+
type UserOf<A> = A extends (...args: any[]) => infer R ? NonNullable<Awaited<R>> : A extends {
|
|
229
229
|
getUser: (...args: any[]) => infer R;
|
|
230
230
|
} ? NonNullable<Awaited<R>> : A extends {
|
|
231
231
|
issuer: any;
|
|
@@ -237,7 +237,7 @@ type AuthEntry = {
|
|
|
237
237
|
user: (ctx: Context) => Promise<any>;
|
|
238
238
|
routes?: (app: Server) => void;
|
|
239
239
|
};
|
|
240
|
-
type AuthSettings = AuthEntry
|
|
240
|
+
type AuthSettings = AuthEntry;
|
|
241
241
|
type LogLevel = "info";
|
|
242
242
|
type Logger = {
|
|
243
243
|
level?: LogLevel;
|
|
@@ -596,11 +596,6 @@ declare function server<U>(options: Omit<Options, "auth"> & {
|
|
|
596
596
|
}): Server<{
|
|
597
597
|
user: NonNullable<Awaited<U>>;
|
|
598
598
|
}>;
|
|
599
|
-
declare function server<A extends readonly AuthOption[]>(options: Omit<Options, "auth"> & {
|
|
600
|
-
auth: A;
|
|
601
|
-
}): Server<{
|
|
602
|
-
user: UserOf<A[number]>;
|
|
603
|
-
}>;
|
|
604
599
|
declare function server<C extends ContextTypes = {}>(options?: Options): Server<C>;
|
|
605
600
|
|
|
606
601
|
export { type AuthClaims, type AuthConfig, type AuthEntry, type AuthFunction, type AuthInstance, type AuthMeta, type AuthOption, type AuthProfile, type AuthSettings, type AuthVerify, type BasicValue, type Body, type BodyMode, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type ContextExtension, type ContextTypes, type Cookie, type CorsSettings, type ExtractPathParams, type FileInfo, type InferParamType, type InlineReply, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type ProviderOptions, type RedirectOption, type Route, type RouteOptions, type RouteSchema, type RouterMethod, type SchemaOutput, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, TypedServerError as ServerError, type Settings, type StandardIssue, type StandardSchemaV1, type Strategy, type Time, type UploadOptions, type UploadedFile, type UserOf, ValidationError, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
|
package/index.js
CHANGED
|
@@ -1016,29 +1016,28 @@ async function verifyJwt(token, secret) {
|
|
|
1016
1016
|
var NAME = "session";
|
|
1017
1017
|
var inCookie = (s) => s === "session" || s === "cookie";
|
|
1018
1018
|
var isSigned = (s) => s === "cookie" || s === "jwt";
|
|
1019
|
-
var UNITS2 = { s: 1, m: 60, h: 3600, d: 86400, w: 604800 };
|
|
1020
1019
|
function seconds(expires) {
|
|
1021
|
-
const
|
|
1022
|
-
if (!
|
|
1023
|
-
return
|
|
1020
|
+
const ms = parse(expires);
|
|
1021
|
+
if (!ms) throw new Error(`Invalid \`expires\`: "${expires}"`);
|
|
1022
|
+
return Math.round(ms / 1e3);
|
|
1024
1023
|
}
|
|
1025
1024
|
var bearer = (ctx) => {
|
|
1026
1025
|
const header = ctx.headers.authorization;
|
|
1027
1026
|
if (!header) return;
|
|
1028
1027
|
const [type2, token] = header.trim().split(" ");
|
|
1029
|
-
if (type2?.toLowerCase() !== "bearer"
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1028
|
+
if (type2?.toLowerCase() !== "bearer") return;
|
|
1029
|
+
if (!token) throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1032
1030
|
return token;
|
|
1033
1031
|
};
|
|
1034
|
-
async function read(ctx,
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
1032
|
+
async function read(ctx, strategy) {
|
|
1033
|
+
const token = inCookie(strategy) ? ctx.cookies[NAME] : bearer(ctx);
|
|
1034
|
+
if (!token) return;
|
|
1035
|
+
const payload = await verifyJwt(token, ctx.options.secrets);
|
|
1036
|
+
if (!payload) {
|
|
1037
|
+
if (inCookie(strategy)) return;
|
|
1038
|
+
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1041
1039
|
}
|
|
1040
|
+
return payload;
|
|
1042
1041
|
}
|
|
1043
1042
|
var meta = (payload, strategy) => ({
|
|
1044
1043
|
issuedAt: new Date(payload.iat * 1e3),
|
|
@@ -1395,31 +1394,28 @@ function parseProviders(given) {
|
|
|
1395
1394
|
var target = async (where, fallback, user, ctx) => typeof where === "function" ? where(user, ctx) : where ?? fallback;
|
|
1396
1395
|
function entry(config2) {
|
|
1397
1396
|
const list = parseProviders(config2.providers);
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
);
|
|
1404
|
-
}
|
|
1397
|
+
const strategy = config2.strategy ?? "session";
|
|
1398
|
+
if (!["session", "cookie", "token", "jwt"].includes(strategy)) {
|
|
1399
|
+
throw new Error(
|
|
1400
|
+
`Unknown strategy "${strategy}"; it takes 'session', 'cookie', 'token' or 'jwt'.`
|
|
1401
|
+
);
|
|
1405
1402
|
}
|
|
1406
1403
|
const expires = config2.expires ?? "30d";
|
|
1404
|
+
seconds(expires);
|
|
1407
1405
|
const { onLogin, getUser, toPublicUser, onLogout } = config2;
|
|
1408
1406
|
if (onLogin && !getUser) {
|
|
1409
1407
|
throw new Error("`onLogin` needs a `getUser`: something has to resolve the id it returns.");
|
|
1410
1408
|
}
|
|
1411
|
-
|
|
1412
|
-
if (
|
|
1413
|
-
if (getUser && !toPublicUser) {
|
|
1414
|
-
throw new Error(
|
|
1415
|
-
`The \`${strategy}\` strategy signs the user into the credential, so it needs a \`toPublicUser\` to say what goes in. Signing the whole row would publish whatever else is on it.`
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
} else if (!getUser) {
|
|
1409
|
+
if (isSigned(strategy)) {
|
|
1410
|
+
if (getUser && !toPublicUser) {
|
|
1419
1411
|
throw new Error(
|
|
1420
|
-
`The \`${strategy}\` strategy
|
|
1412
|
+
`The \`${strategy}\` strategy signs the user into the credential, so it needs a \`toPublicUser\` to say what goes in. Signing the whole row would publish whatever else is on it.`
|
|
1421
1413
|
);
|
|
1422
1414
|
}
|
|
1415
|
+
} else if (!getUser) {
|
|
1416
|
+
throw new Error(
|
|
1417
|
+
`The \`${strategy}\` strategy puts an id in the credential, so it needs a \`getUser\` to resolve it. With no database, use \`cookie\` or \`jwt\`.`
|
|
1418
|
+
);
|
|
1423
1419
|
}
|
|
1424
1420
|
const publicProfile = ({ id, email, name, avatar }) => ({
|
|
1425
1421
|
id,
|
|
@@ -1431,19 +1427,28 @@ function entry(config2) {
|
|
|
1431
1427
|
const loginTo = typeof config2.redirect === "object" ? redirects.login : config2.redirect;
|
|
1432
1428
|
const finish = async (ctx, profile) => {
|
|
1433
1429
|
const payload = getUser ? await (async () => {
|
|
1434
|
-
|
|
1430
|
+
let id;
|
|
1431
|
+
try {
|
|
1432
|
+
id = await onLogin(profile, ctx);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
error.expose = true;
|
|
1435
|
+
throw error;
|
|
1436
|
+
}
|
|
1435
1437
|
if (id === void 0 || id === null) {
|
|
1436
1438
|
throw new Error("`onLogin` must return the id the credential points at");
|
|
1437
1439
|
}
|
|
1438
|
-
if (!isSigned(
|
|
1440
|
+
if (!isSigned(strategy)) return { sub: String(id) };
|
|
1439
1441
|
const user2 = await getUser(String(id), ctx);
|
|
1442
|
+
if (user2 === void 0 || user2 === null) {
|
|
1443
|
+
throw new Error(`getUser returned nothing for the id "${id}" that onLogin just returned`);
|
|
1444
|
+
}
|
|
1440
1445
|
return { user: await toPublicUser(user2) };
|
|
1441
1446
|
})() : { user: publicProfile(profile) };
|
|
1442
1447
|
const signed = { ...payload, provider: profile.provider };
|
|
1443
1448
|
const token = await issue(ctx, signed, expires);
|
|
1444
1449
|
const user = signed.user ?? await getUser(signed.sub, ctx);
|
|
1445
1450
|
const to = await target(loginTo, "/", user, ctx);
|
|
1446
|
-
if (inCookie(
|
|
1451
|
+
if (inCookie(strategy)) {
|
|
1447
1452
|
return cookies("session", {
|
|
1448
1453
|
value: token,
|
|
1449
1454
|
path: "/",
|
|
@@ -1458,9 +1463,8 @@ function entry(config2) {
|
|
|
1458
1463
|
return {
|
|
1459
1464
|
name: "flow",
|
|
1460
1465
|
async user(ctx) {
|
|
1461
|
-
const
|
|
1462
|
-
if (!
|
|
1463
|
-
const { payload, strategy } = found;
|
|
1466
|
+
const payload = await read(ctx, strategy);
|
|
1467
|
+
if (!payload) return;
|
|
1464
1468
|
ctx.auth = meta(payload, strategy);
|
|
1465
1469
|
if (payload.user) return payload.user;
|
|
1466
1470
|
if (!payload.sub) return;
|
|
@@ -1495,8 +1499,10 @@ function entry(config2) {
|
|
|
1495
1499
|
res = await finish(ctx, profile);
|
|
1496
1500
|
} catch (error) {
|
|
1497
1501
|
const to = await target(redirects.error, "/", null, ctx);
|
|
1498
|
-
|
|
1499
|
-
|
|
1502
|
+
let message = "Could not sign you in";
|
|
1503
|
+
if (error?.expose) message = error.message;
|
|
1504
|
+
else console.error(`[server:auth] ${name} callback failed:`, error);
|
|
1505
|
+
res = await redirect(`${to}?error=${encodeURIComponent(message)}`);
|
|
1500
1506
|
}
|
|
1501
1507
|
res.headers.append(
|
|
1502
1508
|
"set-cookie",
|
|
@@ -1507,12 +1513,10 @@ function entry(config2) {
|
|
|
1507
1513
|
app.get(`/auth/callback/${name}`, SPEC, callback);
|
|
1508
1514
|
}
|
|
1509
1515
|
app.post("/auth/logout", SPEC, async (ctx) => {
|
|
1510
|
-
const
|
|
1511
|
-
if (onLogout &&
|
|
1512
|
-
await onLogout(found.payload.sub, ctx);
|
|
1513
|
-
}
|
|
1516
|
+
const payload = await read(ctx, strategy).catch(() => void 0);
|
|
1517
|
+
if (onLogout && payload?.sub) await onLogout(payload.sub, ctx);
|
|
1514
1518
|
const to = await target(redirects.logout, "/", null, ctx);
|
|
1515
|
-
if (!inCookie(
|
|
1519
|
+
if (!inCookie(strategy)) return status(204);
|
|
1516
1520
|
return cookies("session", { value: null }).redirect(to);
|
|
1517
1521
|
});
|
|
1518
1522
|
}
|
|
@@ -1566,9 +1570,8 @@ var bearer2 = (ctx) => {
|
|
|
1566
1570
|
const header = ctx.headers.authorization;
|
|
1567
1571
|
if (!header) return;
|
|
1568
1572
|
const [type2, token] = header.trim().split(" ");
|
|
1569
|
-
if (type2?.toLowerCase() !== "bearer"
|
|
1570
|
-
|
|
1571
|
-
}
|
|
1573
|
+
if (type2?.toLowerCase() !== "bearer") return;
|
|
1574
|
+
if (!token) throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1572
1575
|
return token;
|
|
1573
1576
|
};
|
|
1574
1577
|
function entry2(options) {
|
|
@@ -1585,7 +1588,13 @@ function entry2(options) {
|
|
|
1585
1588
|
async user(ctx) {
|
|
1586
1589
|
const token = options.cookie ? ctx.cookies[options.cookie] : bearer2(ctx);
|
|
1587
1590
|
if (!token) return;
|
|
1588
|
-
|
|
1591
|
+
let claims2;
|
|
1592
|
+
try {
|
|
1593
|
+
claims2 = await check(token, issuer, allowed, claimNames);
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
if (options.cookie) return;
|
|
1596
|
+
throw error;
|
|
1597
|
+
}
|
|
1589
1598
|
ctx.auth = {
|
|
1590
1599
|
issuedAt: new Date((claims2.iat ?? 0) * 1e3),
|
|
1591
1600
|
expiresAt: claims2.exp ? new Date(claims2.exp * 1e3) : void 0,
|
|
@@ -1668,8 +1677,12 @@ var vendors_default = VENDORS;
|
|
|
1668
1677
|
// src/auth/parse.ts
|
|
1669
1678
|
function parseAuth(auth2) {
|
|
1670
1679
|
if (!auth2) return null;
|
|
1671
|
-
|
|
1672
|
-
|
|
1680
|
+
if (Array.isArray(auth2)) {
|
|
1681
|
+
throw new Error(
|
|
1682
|
+
"`auth` takes one method. For several login options, list them under `providers` instead: auth: { providers: ['github', 'google'], ... }."
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
return toEntry(auth2);
|
|
1673
1686
|
}
|
|
1674
1687
|
function vendorEntry(strategy, name) {
|
|
1675
1688
|
const vendor = vendors_default[name];
|
|
@@ -1711,15 +1724,15 @@ function toEntry(auth2) {
|
|
|
1711
1724
|
`Invalid auth "${auth2}": the string form is "<strategy>:<name>", like "cookie:github" to log people in, or "jwt:clerk" to check a token a vendor issued.`
|
|
1712
1725
|
);
|
|
1713
1726
|
}
|
|
1714
|
-
if (vendors_default[name]) return
|
|
1715
|
-
return
|
|
1727
|
+
if (vendors_default[name]) return vendorEntry(strategy, name);
|
|
1728
|
+
return entry({ strategy, providers: name });
|
|
1716
1729
|
}
|
|
1717
1730
|
if (typeof auth2 === "function") {
|
|
1718
|
-
return
|
|
1731
|
+
return { name: "function", user: async (ctx) => auth2(ctx) };
|
|
1719
1732
|
}
|
|
1720
1733
|
if (auth2 && typeof auth2 === "object") {
|
|
1721
|
-
if ("issuer" in auth2) return
|
|
1722
|
-
if ("providers" in auth2) return
|
|
1734
|
+
if ("issuer" in auth2) return entry2(auth2);
|
|
1735
|
+
if ("providers" in auth2) return entry(auth2);
|
|
1723
1736
|
if ("handler" in auth2) {
|
|
1724
1737
|
const instance = auth2;
|
|
1725
1738
|
const path = (instance.path ?? "/api/auth").replace(/\/$/, "");
|
|
@@ -1733,20 +1746,18 @@ function toEntry(auth2) {
|
|
|
1733
1746
|
...ctx.body ? { duplex: "half" } : {}
|
|
1734
1747
|
})
|
|
1735
1748
|
);
|
|
1736
|
-
return
|
|
1737
|
-
{
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
app.delete(wildcard, raw, forward);
|
|
1747
|
-
}
|
|
1749
|
+
return {
|
|
1750
|
+
name: `instance:${path}`,
|
|
1751
|
+
user: async (ctx) => instance.user?.(ctx),
|
|
1752
|
+
routes: (app) => {
|
|
1753
|
+
const wildcard = `${path}/*`;
|
|
1754
|
+
app.get(wildcard, raw, forward);
|
|
1755
|
+
app.post(wildcard, raw, forward);
|
|
1756
|
+
app.put(wildcard, raw, forward);
|
|
1757
|
+
app.patch(wildcard, raw, forward);
|
|
1758
|
+
app.delete(wildcard, raw, forward);
|
|
1748
1759
|
}
|
|
1749
|
-
|
|
1760
|
+
};
|
|
1750
1761
|
}
|
|
1751
1762
|
}
|
|
1752
1763
|
throw new Error(
|
|
@@ -1816,16 +1827,16 @@ var STATUS_TEXT = {
|
|
|
1816
1827
|
502: "Bad Gateway",
|
|
1817
1828
|
503: "Service Unavailable"
|
|
1818
1829
|
};
|
|
1819
|
-
var
|
|
1830
|
+
var UNITS2 = ["b", "kb", "mb", "gb", "tb"];
|
|
1820
1831
|
function formatBytes(bytes) {
|
|
1821
1832
|
if (!bytes || bytes < 0) return "0b";
|
|
1822
1833
|
const i = Math.min(
|
|
1823
1834
|
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
1824
|
-
|
|
1835
|
+
UNITS2.length - 1
|
|
1825
1836
|
);
|
|
1826
1837
|
const value = bytes / 1024 ** i;
|
|
1827
1838
|
const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
1828
|
-
return `${rounded}${
|
|
1839
|
+
return `${rounded}${UNITS2[i]}`;
|
|
1829
1840
|
}
|
|
1830
1841
|
var SCOPE_COLORS = {
|
|
1831
1842
|
start: "green",
|
|
@@ -2014,10 +2025,10 @@ function config(options = {}) {
|
|
|
2014
2025
|
options.auth || env2.AUTH || null
|
|
2015
2026
|
);
|
|
2016
2027
|
}
|
|
2017
|
-
if (settings.auth?.
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
);
|
|
2028
|
+
if (settings.auth?.name === "flow" && settings.secrets[0].startsWith("unsafe-")) {
|
|
2029
|
+
const message = "Auth needs a stable secret: credentials are signed with it, and the random per-process fallback breaks them on restart and across instances. Set the SECRETS environment variable (or the `secrets` option).";
|
|
2030
|
+
if (env2.NODE_ENV === "production") throw new Error(message);
|
|
2031
|
+
console.warn(`[server:auth] ${message}`);
|
|
2021
2032
|
}
|
|
2022
2033
|
if (options.openapi) {
|
|
2023
2034
|
const o = options.openapi;
|
|
@@ -2032,10 +2043,7 @@ function config(options = {}) {
|
|
|
2032
2043
|
});
|
|
2033
2044
|
settings.onResponse = options.onResponse;
|
|
2034
2045
|
const loc = (v) => typeof v === "string" ? v : "enabled";
|
|
2035
|
-
if (settings.auth) {
|
|
2036
|
-
const names = settings.auth.map((one) => one.name).join(", ");
|
|
2037
|
-
log.message("auth", ` enabled`);
|
|
2038
|
-
}
|
|
2046
|
+
if (settings.auth) log.message("auth", `${settings.auth.name} enabled`);
|
|
2039
2047
|
if (settings.public) log.message("public", loc(options.public));
|
|
2040
2048
|
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
2041
2049
|
if (settings.cors) {
|
|
@@ -2412,17 +2420,11 @@ function toWeb(nodeStream) {
|
|
|
2412
2420
|
|
|
2413
2421
|
// src/auth/index.ts
|
|
2414
2422
|
function auth(app) {
|
|
2415
|
-
const
|
|
2423
|
+
const entry3 = app.settings.auth;
|
|
2416
2424
|
app.use(async function middle(ctx) {
|
|
2417
|
-
|
|
2418
|
-
const user = await entry3.user(ctx);
|
|
2419
|
-
if (user) {
|
|
2420
|
-
ctx.user = user;
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2425
|
+
ctx.user = await entry3.user(ctx);
|
|
2424
2426
|
});
|
|
2425
|
-
|
|
2427
|
+
entry3.routes?.(app);
|
|
2426
2428
|
}
|
|
2427
2429
|
|
|
2428
2430
|
// src/helpers/parseRange.ts
|
|
@@ -2664,10 +2666,7 @@ function timer(ctx) {
|
|
|
2664
2666
|
async function socketUser(app, headers2, cookies2) {
|
|
2665
2667
|
if (!app.settings.auth) return void 0;
|
|
2666
2668
|
const ctx = { options: app.settings, headers: headers2, cookies: cookies2 };
|
|
2667
|
-
|
|
2668
|
-
const user = await entry3.user(ctx);
|
|
2669
|
-
if (user) return user;
|
|
2670
|
-
}
|
|
2669
|
+
return app.settings.auth.user(ctx);
|
|
2671
2670
|
}
|
|
2672
2671
|
|
|
2673
2672
|
// src/helpers/wsNode.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.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",
|