@server/next 0.29.0 → 0.31.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 +15 -9
- package/index.js +563 -280
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -32,10 +32,19 @@ type RouteOptions = {
|
|
|
32
32
|
title?: string;
|
|
33
33
|
description?: string;
|
|
34
34
|
};
|
|
35
|
+
type Route = {
|
|
36
|
+
path: string;
|
|
37
|
+
options: RouteOptions;
|
|
38
|
+
fns: Middleware[];
|
|
39
|
+
};
|
|
35
40
|
type Cookie = {
|
|
36
|
-
value?: string;
|
|
41
|
+
value?: string | null;
|
|
37
42
|
path?: string;
|
|
38
43
|
expires?: number | string | Date;
|
|
44
|
+
maxAge?: number;
|
|
45
|
+
httpOnly?: boolean;
|
|
46
|
+
secure?: boolean;
|
|
47
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
39
48
|
};
|
|
40
49
|
type RouterMethod = "*" | Method;
|
|
41
50
|
type Bucket = {
|
|
@@ -77,7 +86,7 @@ type KVStore = {
|
|
|
77
86
|
del: (key: string) => Promise<void | string>;
|
|
78
87
|
keys: () => Promise<string[]>;
|
|
79
88
|
};
|
|
80
|
-
type Provider = "email" | "github";
|
|
89
|
+
type Provider = "email" | "github" | "google" | "microsoft" | "discord" | "facebook" | "apple";
|
|
81
90
|
type Strategy = "cookie" | "jwt" | "token";
|
|
82
91
|
type AuthSession = {
|
|
83
92
|
id: string;
|
|
@@ -247,12 +256,11 @@ declare global {
|
|
|
247
256
|
}
|
|
248
257
|
|
|
249
258
|
type Mids<O extends ServerConfig, Path extends string> = Middleware<O, PathToParams<Path>>[];
|
|
250
|
-
type PathOrMiddle<O extends ServerConfig = object> = string | Middleware<O>;
|
|
251
|
-
type FullRoute = [RouterMethod, string, ...Middleware[]][];
|
|
252
259
|
declare class Router<O extends ServerConfig = object> {
|
|
253
|
-
|
|
260
|
+
middleware: Middleware[];
|
|
261
|
+
handlers: Record<Method, Route[]>;
|
|
254
262
|
self(): this;
|
|
255
|
-
handle(method:
|
|
263
|
+
handle(method: Method, pathOrFn?: any, ...rest: any[]): this;
|
|
256
264
|
socket<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
|
|
257
265
|
socket<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
|
|
258
266
|
socket(...middleware: Middleware<O>[]): this;
|
|
@@ -286,9 +294,7 @@ declare class Router<O extends ServerConfig = object> {
|
|
|
286
294
|
options(...middleware: Middleware<O>[]): this;
|
|
287
295
|
options(options: RouteOptions, ...middleware: Middleware<O>[]): this;
|
|
288
296
|
use(...middleware: Middleware[]): this;
|
|
289
|
-
use(path: string, ...middleware: Middleware[]): this;
|
|
290
297
|
use(router: Router): this;
|
|
291
|
-
use(path: string, router: Router): this;
|
|
292
298
|
}
|
|
293
299
|
declare function router(): Router;
|
|
294
300
|
|
|
@@ -448,4 +454,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
|
|
|
448
454
|
}
|
|
449
455
|
declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
|
|
450
456
|
|
|
451
|
-
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, 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 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 };
|
|
457
|
+
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, 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
|
@@ -43,12 +43,22 @@ ServerError_default.extend({
|
|
|
43
43
|
NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
|
|
44
44
|
NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
|
|
45
45
|
AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
|
|
46
|
-
AUTH_INVALID_TOKEN: "Invalid Authorization token",
|
|
47
|
-
AUTH_INVALID_COOKIE: "Invalid Authorization cookie",
|
|
48
|
-
AUTH_INVALID_HEADER:
|
|
49
|
-
|
|
46
|
+
AUTH_INVALID_TOKEN: { status: 401, message: "Invalid Authorization token" },
|
|
47
|
+
AUTH_INVALID_COOKIE: { status: 401, message: "Invalid Authorization cookie" },
|
|
48
|
+
AUTH_INVALID_HEADER: {
|
|
49
|
+
status: 401,
|
|
50
|
+
message: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)"
|
|
51
|
+
},
|
|
52
|
+
AUTH_INVALID_STRATEGY: {
|
|
53
|
+
status: 401,
|
|
54
|
+
message: "Invalid Authorization type '{strategy}', valid one is '{valid}'"
|
|
55
|
+
},
|
|
56
|
+
AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
|
|
50
57
|
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
|
|
51
|
-
AUTH_INVALID_PROVIDER:
|
|
58
|
+
AUTH_INVALID_PROVIDER: {
|
|
59
|
+
status: 401,
|
|
60
|
+
message: "Invalid provider '{provider}', valid ones are: '{valid}'"
|
|
61
|
+
},
|
|
52
62
|
AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
|
|
53
63
|
AUTH_NO_USER: {
|
|
54
64
|
status: 401,
|
|
@@ -97,98 +107,6 @@ function clientIp(headers2, opts = {}) {
|
|
|
97
107
|
return normalize(remoteAddress);
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
// src/auth/updateUser.ts
|
|
101
|
-
async function updateUser(user, auth2, store) {
|
|
102
|
-
if (auth2.provider === "email") {
|
|
103
|
-
return await store.set(auth2.email, user);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/auth/providers/email.ts
|
|
108
|
-
var createSession = async (user, ctx) => {
|
|
109
|
-
const { strategy, session: session2, cleanUser, redirect: redirect2 = "/user" } = ctx.options.auth;
|
|
110
|
-
user = await cleanUser(user);
|
|
111
|
-
const id = createId();
|
|
112
|
-
const provider = "email";
|
|
113
|
-
ctx.user = {
|
|
114
|
-
id,
|
|
115
|
-
strategy,
|
|
116
|
-
provider,
|
|
117
|
-
email: user.email
|
|
118
|
-
};
|
|
119
|
-
await session2.set(
|
|
120
|
-
id,
|
|
121
|
-
{ id, strategy, provider, user: user.email },
|
|
122
|
-
{ expires: "1w" }
|
|
123
|
-
);
|
|
124
|
-
if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
|
|
125
|
-
if (strategy.includes("token")) {
|
|
126
|
-
return status(201).json({ ...user, token: id });
|
|
127
|
-
}
|
|
128
|
-
if (strategy.includes("cookie")) {
|
|
129
|
-
return status(302).cookies({ authentication: id }).redirect(redirect2);
|
|
130
|
-
}
|
|
131
|
-
if (strategy.includes("jwt")) {
|
|
132
|
-
throw new Error("JWT auth not supported yet");
|
|
133
|
-
}
|
|
134
|
-
if (strategy.includes("key")) {
|
|
135
|
-
throw new Error("Key auth not supported yet");
|
|
136
|
-
}
|
|
137
|
-
throw new Error("Unknown auth type");
|
|
138
|
-
};
|
|
139
|
-
async function emailLogin(ctx) {
|
|
140
|
-
const { email, password } = ctx.body;
|
|
141
|
-
if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
|
|
142
|
-
if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
|
|
143
|
-
if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
|
|
144
|
-
if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
|
|
145
|
-
const store = ctx.options.auth.store;
|
|
146
|
-
if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
|
|
147
|
-
const user = await store.get(email);
|
|
148
|
-
const isValid = await verify(password, user.password);
|
|
149
|
-
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
150
|
-
return createSession(user, ctx);
|
|
151
|
-
}
|
|
152
|
-
async function emailRegister(ctx) {
|
|
153
|
-
const { email, password, ...data } = ctx.body;
|
|
154
|
-
if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
|
|
155
|
-
if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
|
|
156
|
-
if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
|
|
157
|
-
if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
|
|
158
|
-
const store = ctx.options.auth.store;
|
|
159
|
-
if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
|
|
160
|
-
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
161
|
-
const user = {
|
|
162
|
-
id: createId(email),
|
|
163
|
-
strategy: ctx.options.auth.strategy,
|
|
164
|
-
provider: "email",
|
|
165
|
-
email,
|
|
166
|
-
password: await hash(password),
|
|
167
|
-
time,
|
|
168
|
-
...data
|
|
169
|
-
};
|
|
170
|
-
await store.set(email, user);
|
|
171
|
-
return createSession(user, ctx);
|
|
172
|
-
}
|
|
173
|
-
async function emailResetPassword() {
|
|
174
|
-
}
|
|
175
|
-
async function emailUpdatePassword(ctx) {
|
|
176
|
-
const passwords = ctx.body;
|
|
177
|
-
const fullUser = await ctx.options.auth.store.get(ctx.user.email);
|
|
178
|
-
if (!fullUser) throw ServerError_default.AUTH_NO_USER();
|
|
179
|
-
const isValid = await verify(passwords.previous, fullUser.password);
|
|
180
|
-
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
181
|
-
fullUser.password = await hash(passwords.updated);
|
|
182
|
-
await updateUser(fullUser, ctx.user, ctx.options.auth.store);
|
|
183
|
-
return 200;
|
|
184
|
-
}
|
|
185
|
-
var email_default = {
|
|
186
|
-
login: emailLogin,
|
|
187
|
-
register: emailRegister,
|
|
188
|
-
reset: emailResetPassword,
|
|
189
|
-
password: emailUpdatePassword
|
|
190
|
-
};
|
|
191
|
-
|
|
192
110
|
// src/helpers/isReadableStream.ts
|
|
193
111
|
function isReadableStream(obj) {
|
|
194
112
|
return obj !== null && typeof obj === "object" && typeof obj.pipe === "function" && typeof obj.read === "function" && typeof obj.on === "function";
|
|
@@ -314,6 +232,379 @@ var json = (...args) => r().json(...args);
|
|
|
314
232
|
var file = (...args) => r().file(...args);
|
|
315
233
|
var redirect = (...args) => r().redirect(...args);
|
|
316
234
|
|
|
235
|
+
// src/auth/finishLogin.ts
|
|
236
|
+
async function finishLogin(ctx, input) {
|
|
237
|
+
const settings = ctx.options.auth;
|
|
238
|
+
const { strategy, cleanUser } = settings;
|
|
239
|
+
const key = String(input.key);
|
|
240
|
+
const auth2 = {
|
|
241
|
+
id: createId(),
|
|
242
|
+
strategy,
|
|
243
|
+
provider: input.provider,
|
|
244
|
+
user: key,
|
|
245
|
+
email: input.email,
|
|
246
|
+
time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
|
|
247
|
+
};
|
|
248
|
+
let user = input.user;
|
|
249
|
+
if (input.store !== false) {
|
|
250
|
+
const existing = await settings.store.get(key);
|
|
251
|
+
user = { ...existing ?? {}, ...input.user };
|
|
252
|
+
}
|
|
253
|
+
user = await cleanUser(user);
|
|
254
|
+
if (input.store !== false) await settings.store.set(key, user);
|
|
255
|
+
await settings.session.set(auth2.id, auth2, { expires: "1w" });
|
|
256
|
+
if (strategy.includes("token")) {
|
|
257
|
+
return status(201).json({ ...user, token: auth2.id });
|
|
258
|
+
}
|
|
259
|
+
if (strategy.includes("cookie")) {
|
|
260
|
+
return cookies("authentication", {
|
|
261
|
+
value: auth2.id,
|
|
262
|
+
path: "/",
|
|
263
|
+
httpOnly: true,
|
|
264
|
+
secure: ctx.platform.production,
|
|
265
|
+
sameSite: "Lax"
|
|
266
|
+
}).redirect(settings.redirect);
|
|
267
|
+
}
|
|
268
|
+
if (strategy.includes("jwt")) throw new Error("JWT auth not supported yet");
|
|
269
|
+
if (strategy.includes("key")) throw new Error("Key auth not supported yet");
|
|
270
|
+
throw new Error("Unknown auth type");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/helpers/createCookies.ts
|
|
274
|
+
var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
|
|
275
|
+
var times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
|
|
276
|
+
parse.millisecond = parse.ms = 1e-3;
|
|
277
|
+
parse.second = parse.sec = parse.s = parse[""] = 1;
|
|
278
|
+
parse.minute = parse.min = parse.m = parse.s * 60;
|
|
279
|
+
parse.hour = parse.hr = parse.h = parse.m * 60;
|
|
280
|
+
parse.day = parse.d = parse.h * 24;
|
|
281
|
+
parse.week = parse.wk = parse.w = parse.d * 7;
|
|
282
|
+
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
283
|
+
parse.month = parse.b = parse.y / 12;
|
|
284
|
+
function parse(str) {
|
|
285
|
+
if (str === null || str === void 0) return null;
|
|
286
|
+
if (typeof str === "number") return str;
|
|
287
|
+
if (typeof str !== "string") {
|
|
288
|
+
throw new Error(`Not a string: ${str} (${typeof str})`);
|
|
289
|
+
}
|
|
290
|
+
str = str.toLowerCase().replace(/[,_]/g, "");
|
|
291
|
+
const [_, value, units] = times.exec(str) || [];
|
|
292
|
+
if (!units) return null;
|
|
293
|
+
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
294
|
+
if (!unitValue) return null;
|
|
295
|
+
const result = unitValue * parseFloat(value);
|
|
296
|
+
return Math.abs(Math.round(result * 1e3));
|
|
297
|
+
}
|
|
298
|
+
function normalizeExpires(expires) {
|
|
299
|
+
if (expires === null || expires === void 0) return void 0;
|
|
300
|
+
if (expires === 0) return EXPIRED2;
|
|
301
|
+
if (typeof expires === "string") {
|
|
302
|
+
if (/^[\d._]+\w+$/.test(expires)) {
|
|
303
|
+
return new Date(Date.now() + parse(expires)).toUTCString();
|
|
304
|
+
} else {
|
|
305
|
+
return expires;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (typeof expires === "number") {
|
|
309
|
+
return new Date(Date.now() + expires).toUTCString();
|
|
310
|
+
}
|
|
311
|
+
if (expires instanceof Date) {
|
|
312
|
+
return expires.toUTCString();
|
|
313
|
+
}
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
function createCookies(key, val) {
|
|
317
|
+
if (val.value === null) val.expires = EXPIRED2;
|
|
318
|
+
const { value, path: path2, expires, maxAge, httpOnly, secure, sameSite } = val;
|
|
319
|
+
let str = `${key}=${value || ""};Path=${path2 || "/"}`;
|
|
320
|
+
if (typeof expires !== "undefined") str += `;Expires=${normalizeExpires(expires)}`;
|
|
321
|
+
if (typeof maxAge === "number") str += `;Max-Age=${maxAge}`;
|
|
322
|
+
if (httpOnly) str += ";HttpOnly";
|
|
323
|
+
if (secure) str += ";Secure";
|
|
324
|
+
if (sameSite) str += `;SameSite=${sameSite}`;
|
|
325
|
+
return str;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/auth/state.ts
|
|
329
|
+
var NAME = "oauth_state";
|
|
330
|
+
function startState(ctx, crossSite = false) {
|
|
331
|
+
const state = createId();
|
|
332
|
+
return {
|
|
333
|
+
state,
|
|
334
|
+
cookie: {
|
|
335
|
+
value: state,
|
|
336
|
+
path: "/",
|
|
337
|
+
expires: "10m",
|
|
338
|
+
httpOnly: true,
|
|
339
|
+
secure: crossSite || ctx.platform.production,
|
|
340
|
+
sameSite: crossSite ? "None" : "Lax"
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function checkState(ctx, received) {
|
|
345
|
+
const expected = ctx.cookies[NAME];
|
|
346
|
+
if (!expected || !received || expected !== received) {
|
|
347
|
+
throw ServerError_default.AUTH_INVALID_STATE();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function clearState() {
|
|
351
|
+
return createCookies(NAME, { value: null });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/auth/providers/apple.ts
|
|
355
|
+
var AUTHORIZE = "https://appleid.apple.com/auth/authorize";
|
|
356
|
+
var TOKEN = "https://appleid.apple.com/auth/token";
|
|
357
|
+
var b64url = (data) => {
|
|
358
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
359
|
+
let bin = "";
|
|
360
|
+
for (const byte of bytes) bin += String.fromCharCode(byte);
|
|
361
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
362
|
+
};
|
|
363
|
+
var b64urlJson = (segment) => {
|
|
364
|
+
let b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
365
|
+
b64 += "=".repeat((4 - b64.length % 4) % 4);
|
|
366
|
+
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
367
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
368
|
+
};
|
|
369
|
+
var clientSecret = async () => {
|
|
370
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
371
|
+
const header = { alg: "ES256", kid: env.APPLE_KEY_ID, typ: "JWT" };
|
|
372
|
+
const payload = {
|
|
373
|
+
iss: env.APPLE_TEAM_ID,
|
|
374
|
+
iat: now,
|
|
375
|
+
exp: now + 3600,
|
|
376
|
+
aud: "https://appleid.apple.com",
|
|
377
|
+
sub: env.APPLE_ID
|
|
378
|
+
};
|
|
379
|
+
const data = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(payload))}`;
|
|
380
|
+
const pem = String(env.APPLE_PRIVATE_KEY).replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
|
|
381
|
+
const der = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
|
|
382
|
+
const key = await crypto.subtle.importKey(
|
|
383
|
+
"pkcs8",
|
|
384
|
+
der,
|
|
385
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
386
|
+
false,
|
|
387
|
+
["sign"]
|
|
388
|
+
);
|
|
389
|
+
const sig = await crypto.subtle.sign(
|
|
390
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
391
|
+
key,
|
|
392
|
+
new TextEncoder().encode(data)
|
|
393
|
+
);
|
|
394
|
+
return `${data}.${b64url(new Uint8Array(sig))}`;
|
|
395
|
+
};
|
|
396
|
+
var login = (ctx) => {
|
|
397
|
+
const { state, cookie } = startState(ctx, true);
|
|
398
|
+
const params = new URLSearchParams({
|
|
399
|
+
client_id: env.APPLE_ID,
|
|
400
|
+
redirect_uri: `${ctx.url.origin}/auth/callback/apple`,
|
|
401
|
+
response_type: "code",
|
|
402
|
+
scope: "name email",
|
|
403
|
+
// Requesting scopes forces Apple to POST the result back (form_post)
|
|
404
|
+
response_mode: "form_post",
|
|
405
|
+
state
|
|
406
|
+
});
|
|
407
|
+
return cookies("oauth_state", cookie).redirect(`${AUTHORIZE}?${params}`);
|
|
408
|
+
};
|
|
409
|
+
var callback = async (ctx) => {
|
|
410
|
+
const body = ctx.body || {};
|
|
411
|
+
checkState(ctx, body.state);
|
|
412
|
+
const tokenRes = await fetch(TOKEN, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: {
|
|
415
|
+
accept: "application/json",
|
|
416
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
417
|
+
},
|
|
418
|
+
body: new URLSearchParams({
|
|
419
|
+
client_id: env.APPLE_ID,
|
|
420
|
+
client_secret: await clientSecret(),
|
|
421
|
+
code: body.code,
|
|
422
|
+
grant_type: "authorization_code",
|
|
423
|
+
redirect_uri: `${ctx.url.origin}/auth/callback/apple`
|
|
424
|
+
})
|
|
425
|
+
});
|
|
426
|
+
if (!tokenRes.ok) throw new Error("apple: token exchange failed");
|
|
427
|
+
const token = await tokenRes.json();
|
|
428
|
+
const claims = b64urlJson(token.id_token.split(".")[1]);
|
|
429
|
+
let name;
|
|
430
|
+
if (body.user) {
|
|
431
|
+
const parsed = JSON.parse(body.user).name;
|
|
432
|
+
if (parsed) name = `${parsed.firstName} ${parsed.lastName}`.trim();
|
|
433
|
+
}
|
|
434
|
+
const res = await finishLogin(ctx, {
|
|
435
|
+
provider: "apple",
|
|
436
|
+
key: claims.sub,
|
|
437
|
+
email: claims.email,
|
|
438
|
+
user: { id: claims.sub, name, email: claims.email }
|
|
439
|
+
});
|
|
440
|
+
res.headers.append("set-cookie", clearState());
|
|
441
|
+
return res;
|
|
442
|
+
};
|
|
443
|
+
var apple_default = { login, callback };
|
|
444
|
+
|
|
445
|
+
// src/auth/providers/oauth.ts
|
|
446
|
+
function oauthProvider(config2) {
|
|
447
|
+
const KEY = config2.name.toUpperCase();
|
|
448
|
+
const callbackUrl = (ctx) => `${ctx.url.origin}/auth/callback/${config2.name}`;
|
|
449
|
+
const login3 = (ctx) => {
|
|
450
|
+
const { state, cookie } = startState(ctx);
|
|
451
|
+
const params = new URLSearchParams({
|
|
452
|
+
client_id: env[`${KEY}_ID`],
|
|
453
|
+
redirect_uri: callbackUrl(ctx),
|
|
454
|
+
response_type: "code",
|
|
455
|
+
scope: config2.scope,
|
|
456
|
+
state
|
|
457
|
+
});
|
|
458
|
+
return cookies("oauth_state", cookie).redirect(
|
|
459
|
+
`${config2.authorizeUrl}?${params}`
|
|
460
|
+
);
|
|
461
|
+
};
|
|
462
|
+
const callback3 = async (ctx) => {
|
|
463
|
+
checkState(ctx, ctx.url.query.state);
|
|
464
|
+
const tokenRes = await fetch(config2.tokenUrl, {
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: {
|
|
467
|
+
accept: "application/json",
|
|
468
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
469
|
+
},
|
|
470
|
+
body: new URLSearchParams({
|
|
471
|
+
client_id: env[`${KEY}_ID`],
|
|
472
|
+
client_secret: env[`${KEY}_SECRET`],
|
|
473
|
+
code: ctx.url.query.code,
|
|
474
|
+
grant_type: "authorization_code",
|
|
475
|
+
redirect_uri: callbackUrl(ctx)
|
|
476
|
+
})
|
|
477
|
+
});
|
|
478
|
+
if (!tokenRes.ok) throw new Error(`${config2.name}: token exchange failed`);
|
|
479
|
+
const token = await tokenRes.json();
|
|
480
|
+
const profileRes = await fetch(config2.profileUrl, {
|
|
481
|
+
headers: {
|
|
482
|
+
accept: "application/json",
|
|
483
|
+
authorization: `Bearer ${token.access_token}`
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
if (!profileRes.ok) throw new Error(`${config2.name}: profile fetch failed`);
|
|
487
|
+
const profile = config2.profile(await profileRes.json());
|
|
488
|
+
const res = await finishLogin(ctx, {
|
|
489
|
+
provider: config2.name,
|
|
490
|
+
key: profile.id,
|
|
491
|
+
email: profile.email,
|
|
492
|
+
user: {
|
|
493
|
+
id: profile.id,
|
|
494
|
+
name: profile.name,
|
|
495
|
+
email: profile.email,
|
|
496
|
+
picture: profile.picture
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
res.headers.append("set-cookie", clearState());
|
|
500
|
+
return res;
|
|
501
|
+
};
|
|
502
|
+
return { login: login3, callback: callback3 };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/auth/providers/discord.ts
|
|
506
|
+
var discord_default = oauthProvider({
|
|
507
|
+
name: "discord",
|
|
508
|
+
authorizeUrl: "https://discord.com/oauth2/authorize",
|
|
509
|
+
tokenUrl: "https://discord.com/api/oauth2/token",
|
|
510
|
+
profileUrl: "https://discord.com/api/users/@me",
|
|
511
|
+
scope: "identify email",
|
|
512
|
+
profile: (p) => ({
|
|
513
|
+
id: p.id,
|
|
514
|
+
email: p.email,
|
|
515
|
+
name: p.global_name || p.username,
|
|
516
|
+
picture: p.avatar ? `https://cdn.discordapp.com/avatars/${p.id}/${p.avatar}.png` : void 0
|
|
517
|
+
})
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// src/auth/updateUser.ts
|
|
521
|
+
async function updateUser(user, auth2, store) {
|
|
522
|
+
if (auth2.provider === "email") {
|
|
523
|
+
return await store.set(auth2.email, user);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/auth/providers/email.ts
|
|
528
|
+
async function emailLogin(ctx) {
|
|
529
|
+
const { email, password } = ctx.body;
|
|
530
|
+
if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
|
|
531
|
+
if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
|
|
532
|
+
if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
|
|
533
|
+
if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
|
|
534
|
+
const store = ctx.options.auth.store;
|
|
535
|
+
if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
|
|
536
|
+
const user = await store.get(email);
|
|
537
|
+
const isValid = await verify(password, user.password);
|
|
538
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
539
|
+
return finishLogin(ctx, {
|
|
540
|
+
provider: "email",
|
|
541
|
+
key: user.email,
|
|
542
|
+
email: user.email,
|
|
543
|
+
user,
|
|
544
|
+
store: false
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
async function emailRegister(ctx) {
|
|
548
|
+
const { email, password, ...data } = ctx.body;
|
|
549
|
+
if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
|
|
550
|
+
if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
|
|
551
|
+
if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
|
|
552
|
+
if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
|
|
553
|
+
const store = ctx.options.auth.store;
|
|
554
|
+
if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
|
|
555
|
+
const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
|
|
556
|
+
const user = {
|
|
557
|
+
id: createId(email),
|
|
558
|
+
strategy: ctx.options.auth.strategy,
|
|
559
|
+
provider: "email",
|
|
560
|
+
email,
|
|
561
|
+
password: await hash(password),
|
|
562
|
+
time,
|
|
563
|
+
...data
|
|
564
|
+
};
|
|
565
|
+
await store.set(email, user);
|
|
566
|
+
return finishLogin(ctx, {
|
|
567
|
+
provider: "email",
|
|
568
|
+
key: email,
|
|
569
|
+
email,
|
|
570
|
+
user,
|
|
571
|
+
store: false
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
async function emailResetPassword() {
|
|
575
|
+
}
|
|
576
|
+
async function emailUpdatePassword(ctx) {
|
|
577
|
+
const passwords = ctx.body;
|
|
578
|
+
const fullUser = await ctx.options.auth.store.get(ctx.user.email);
|
|
579
|
+
if (!fullUser) throw ServerError_default.AUTH_NO_USER();
|
|
580
|
+
const isValid = await verify(passwords.previous, fullUser.password);
|
|
581
|
+
if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
|
|
582
|
+
fullUser.password = await hash(passwords.updated);
|
|
583
|
+
await updateUser(fullUser, ctx.user, ctx.options.auth.store);
|
|
584
|
+
return 200;
|
|
585
|
+
}
|
|
586
|
+
var email_default = {
|
|
587
|
+
login: emailLogin,
|
|
588
|
+
register: emailRegister,
|
|
589
|
+
reset: emailResetPassword,
|
|
590
|
+
password: emailUpdatePassword
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/auth/providers/facebook.ts
|
|
594
|
+
var facebook_default = oauthProvider({
|
|
595
|
+
name: "facebook",
|
|
596
|
+
authorizeUrl: "https://www.facebook.com/v18.0/dialog/oauth",
|
|
597
|
+
tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
|
|
598
|
+
profileUrl: "https://graph.facebook.com/me?fields=id,name,email,picture",
|
|
599
|
+
scope: "email public_profile",
|
|
600
|
+
profile: (p) => ({
|
|
601
|
+
id: p.id,
|
|
602
|
+
email: p.email,
|
|
603
|
+
name: p.name,
|
|
604
|
+
picture: p.picture?.data?.url
|
|
605
|
+
})
|
|
606
|
+
});
|
|
607
|
+
|
|
317
608
|
// src/auth/providers/github.ts
|
|
318
609
|
var oauth = async (code) => {
|
|
319
610
|
const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
|
|
@@ -337,9 +628,15 @@ var oauth = async (code) => {
|
|
|
337
628
|
});
|
|
338
629
|
};
|
|
339
630
|
};
|
|
340
|
-
var
|
|
341
|
-
|
|
342
|
-
|
|
631
|
+
var login2 = (ctx) => {
|
|
632
|
+
const { state, cookie } = startState(ctx);
|
|
633
|
+
const params = new URLSearchParams({
|
|
634
|
+
client_id: env.GITHUB_ID,
|
|
635
|
+
scope: "user:email",
|
|
636
|
+
state
|
|
637
|
+
});
|
|
638
|
+
return cookies("oauth_state", cookie).redirect(
|
|
639
|
+
`https://github.com/login/oauth/authorize?${params}`
|
|
343
640
|
);
|
|
344
641
|
};
|
|
345
642
|
var getUserProfile = async (code) => {
|
|
@@ -351,47 +648,67 @@ var getUserProfile = async (code) => {
|
|
|
351
648
|
const email = emails.sort((a) => a.primary ? -1 : 1)[0]?.email;
|
|
352
649
|
return { ...profile, email };
|
|
353
650
|
};
|
|
354
|
-
var
|
|
355
|
-
|
|
651
|
+
var callback2 = async (ctx) => {
|
|
652
|
+
checkState(ctx, ctx.url.query.state);
|
|
356
653
|
const profile = await getUserProfile(ctx.url.query.code);
|
|
357
|
-
const
|
|
358
|
-
id: createId(),
|
|
359
|
-
strategy,
|
|
654
|
+
const res = await finishLogin(ctx, {
|
|
360
655
|
provider: "github",
|
|
361
|
-
|
|
656
|
+
key: profile.id,
|
|
362
657
|
email: profile.email,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
picture: profile.avatar_url,
|
|
372
|
-
location: profile.location,
|
|
373
|
-
created: profile.created_at
|
|
658
|
+
user: {
|
|
659
|
+
id: profile.id,
|
|
660
|
+
name: profile.name,
|
|
661
|
+
email: profile.email,
|
|
662
|
+
picture: profile.avatar_url,
|
|
663
|
+
location: profile.location,
|
|
664
|
+
created: profile.created_at
|
|
665
|
+
}
|
|
374
666
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (auth2.strategy.includes("token")) {
|
|
378
|
-
return status(201).json({ ...user, token: auth2.id });
|
|
379
|
-
}
|
|
380
|
-
if (auth2.strategy.includes("cookie")) {
|
|
381
|
-
return status(302).cookies({ authentication: auth2.id }).redirect(redirect2);
|
|
382
|
-
}
|
|
383
|
-
if (auth2.strategy.includes("jwt")) {
|
|
384
|
-
throw new Error("JWT auth not supported yet");
|
|
385
|
-
}
|
|
386
|
-
if (auth2.strategy.includes("key")) {
|
|
387
|
-
throw new Error("Key auth not supported yet");
|
|
388
|
-
}
|
|
389
|
-
throw new Error("Unknown auth type");
|
|
667
|
+
res.headers.append("set-cookie", clearState());
|
|
668
|
+
return res;
|
|
390
669
|
};
|
|
391
|
-
var github_default = { login, callback };
|
|
670
|
+
var github_default = { login: login2, callback: callback2 };
|
|
671
|
+
|
|
672
|
+
// src/auth/providers/google.ts
|
|
673
|
+
var google_default = oauthProvider({
|
|
674
|
+
name: "google",
|
|
675
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
676
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
677
|
+
profileUrl: "https://openidconnect.googleapis.com/v1/userinfo",
|
|
678
|
+
scope: "openid email profile",
|
|
679
|
+
profile: (p) => ({
|
|
680
|
+
id: p.sub,
|
|
681
|
+
email: p.email,
|
|
682
|
+
name: p.name,
|
|
683
|
+
picture: p.picture
|
|
684
|
+
})
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// src/auth/providers/microsoft.ts
|
|
688
|
+
var microsoft_default = oauthProvider({
|
|
689
|
+
name: "microsoft",
|
|
690
|
+
authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
691
|
+
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
692
|
+
profileUrl: "https://graph.microsoft.com/v1.0/me",
|
|
693
|
+
scope: "openid email profile User.Read",
|
|
694
|
+
profile: (p) => ({
|
|
695
|
+
// Personal accounts expose `userPrincipalName` rather than `mail`
|
|
696
|
+
id: p.id,
|
|
697
|
+
email: p.mail || p.userPrincipalName,
|
|
698
|
+
name: p.displayName
|
|
699
|
+
})
|
|
700
|
+
});
|
|
392
701
|
|
|
393
702
|
// src/auth/providers/index.ts
|
|
394
|
-
var providers_default = {
|
|
703
|
+
var providers_default = {
|
|
704
|
+
apple: apple_default,
|
|
705
|
+
discord: discord_default,
|
|
706
|
+
email: email_default,
|
|
707
|
+
facebook: facebook_default,
|
|
708
|
+
github: github_default,
|
|
709
|
+
google: google_default,
|
|
710
|
+
microsoft: microsoft_default
|
|
711
|
+
};
|
|
395
712
|
|
|
396
713
|
// src/auth/parseAuthOptions.ts
|
|
397
714
|
var defaultRedirect = "/user";
|
|
@@ -884,71 +1201,25 @@ function applyCors(res, ctx) {
|
|
|
884
1201
|
}
|
|
885
1202
|
}
|
|
886
1203
|
|
|
887
|
-
// src/helpers/createCookies.ts
|
|
888
|
-
var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
|
|
889
|
-
var times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
|
|
890
|
-
parse.millisecond = parse.ms = 1e-3;
|
|
891
|
-
parse.second = parse.sec = parse.s = parse[""] = 1;
|
|
892
|
-
parse.minute = parse.min = parse.m = parse.s * 60;
|
|
893
|
-
parse.hour = parse.hr = parse.h = parse.m * 60;
|
|
894
|
-
parse.day = parse.d = parse.h * 24;
|
|
895
|
-
parse.week = parse.wk = parse.w = parse.d * 7;
|
|
896
|
-
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
897
|
-
parse.month = parse.b = parse.y / 12;
|
|
898
|
-
function parse(str) {
|
|
899
|
-
if (str === null || str === void 0) return null;
|
|
900
|
-
if (typeof str === "number") return str;
|
|
901
|
-
if (typeof str !== "string") {
|
|
902
|
-
throw new Error(`Not a string: ${str} (${typeof str})`);
|
|
903
|
-
}
|
|
904
|
-
str = str.toLowerCase().replace(/[,_]/g, "");
|
|
905
|
-
const [_, value, units] = times.exec(str) || [];
|
|
906
|
-
if (!units) return null;
|
|
907
|
-
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
908
|
-
if (!unitValue) return null;
|
|
909
|
-
const result = unitValue * parseFloat(value);
|
|
910
|
-
return Math.abs(Math.round(result * 1e3));
|
|
911
|
-
}
|
|
912
|
-
function normalizeExpires(expires) {
|
|
913
|
-
if (expires === null || expires === void 0) return void 0;
|
|
914
|
-
if (expires === 0) return EXPIRED2;
|
|
915
|
-
if (typeof expires === "string") {
|
|
916
|
-
if (/^[\d._]+\w+$/.test(expires)) {
|
|
917
|
-
return new Date(Date.now() + parse(expires)).toUTCString();
|
|
918
|
-
} else {
|
|
919
|
-
return expires;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
if (typeof expires === "number") {
|
|
923
|
-
return new Date(Date.now() + expires).toUTCString();
|
|
924
|
-
}
|
|
925
|
-
if (expires instanceof Date) {
|
|
926
|
-
return expires.toUTCString();
|
|
927
|
-
}
|
|
928
|
-
return void 0;
|
|
929
|
-
}
|
|
930
|
-
function createCookies(key, val) {
|
|
931
|
-
if (val.value === null) val.expires = EXPIRED2;
|
|
932
|
-
const { value, path: path2, expires } = val;
|
|
933
|
-
const pathPart = `;Path=${path2 || "/"}`;
|
|
934
|
-
const expiresStr = normalizeExpires(expires);
|
|
935
|
-
const expiresPart = typeof expires !== "undefined" ? `;Expires=${expiresStr}` : "";
|
|
936
|
-
return `${key}=${value || ""}${pathPart}${expiresPart}`;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
1204
|
// src/helpers/createWebsocket.ts
|
|
940
1205
|
function createWebsocket(sockets, handlers) {
|
|
1206
|
+
const run = (event, socket, body) => {
|
|
1207
|
+
const routes = handlers.socket?.filter((r2) => r2.path === event) ?? [];
|
|
1208
|
+
for (const route of routes) {
|
|
1209
|
+
for (const fn of route.fns) {
|
|
1210
|
+
fn({ socket, sockets, body });
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
941
1214
|
return {
|
|
942
|
-
message:
|
|
943
|
-
handlers.socket?.filter((s) => s[1] === "message")?.map((s) => s[2]({ socket, sockets, body }));
|
|
944
|
-
},
|
|
1215
|
+
message: (socket, body) => run("message", socket, body),
|
|
945
1216
|
open: (socket) => {
|
|
946
1217
|
sockets.push(socket);
|
|
947
|
-
|
|
1218
|
+
run("open", socket);
|
|
948
1219
|
},
|
|
949
1220
|
close: (socket) => {
|
|
950
1221
|
sockets.splice(sockets.indexOf(socket), 1);
|
|
951
|
-
|
|
1222
|
+
run("close", socket);
|
|
952
1223
|
}
|
|
953
1224
|
};
|
|
954
1225
|
}
|
|
@@ -1155,18 +1426,23 @@ function validate(ctx, schema) {
|
|
|
1155
1426
|
}
|
|
1156
1427
|
|
|
1157
1428
|
// src/helpers/handleRequest.ts
|
|
1158
|
-
async function handleRequest(
|
|
1159
|
-
const res = await getResponse(
|
|
1429
|
+
async function handleRequest(app, ctx) {
|
|
1430
|
+
const res = await getResponse(app, ctx);
|
|
1160
1431
|
if (res) ctx.options.log.request(ctx, res);
|
|
1161
1432
|
return res;
|
|
1162
1433
|
}
|
|
1163
|
-
async function getResponse(
|
|
1434
|
+
async function getResponse(app, ctx) {
|
|
1164
1435
|
try {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1436
|
+
let matched = false;
|
|
1437
|
+
for (const route of app.handlers[ctx.method]) {
|
|
1438
|
+
const params = pathPattern(route.path, ctx.url.pathname || "/");
|
|
1439
|
+
if (!params) continue;
|
|
1440
|
+
matched = true;
|
|
1441
|
+
define(ctx.url, "params", () => params);
|
|
1442
|
+
if (Object.keys(route.options).length) {
|
|
1443
|
+
ctx.options = { ...app.settings, ...route.options };
|
|
1444
|
+
}
|
|
1445
|
+
for (const cb of route.fns) {
|
|
1170
1446
|
if (typeof cb === "function") {
|
|
1171
1447
|
const res = await cb(ctx);
|
|
1172
1448
|
const out = await parseResponse(res, ctx);
|
|
@@ -1175,7 +1451,13 @@ async function getResponse(handlers, ctx) {
|
|
|
1175
1451
|
validate(ctx, cb);
|
|
1176
1452
|
}
|
|
1177
1453
|
}
|
|
1178
|
-
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
if (!matched) {
|
|
1457
|
+
for (const mw of app.middleware) {
|
|
1458
|
+
const out = await parseResponse(await mw(ctx), ctx);
|
|
1459
|
+
if (out) return out;
|
|
1460
|
+
}
|
|
1179
1461
|
}
|
|
1180
1462
|
if (ctx.platform.provider === "netlify") return;
|
|
1181
1463
|
throw new ServerError_default("NOT_FOUND", 404, "Not Found");
|
|
@@ -1594,7 +1876,7 @@ async function logout(ctx) {
|
|
|
1594
1876
|
return { token: null };
|
|
1595
1877
|
}
|
|
1596
1878
|
if (strategy.includes("cookie")) {
|
|
1597
|
-
return cookies({
|
|
1879
|
+
return cookies({ authentication: null }).redirect("/");
|
|
1598
1880
|
}
|
|
1599
1881
|
if (strategy.includes("jwt")) {
|
|
1600
1882
|
throw new Error("JWT auth not supported yet");
|
|
@@ -1606,18 +1888,37 @@ async function logout(ctx) {
|
|
|
1606
1888
|
}
|
|
1607
1889
|
|
|
1608
1890
|
// src/auth/index.ts
|
|
1891
|
+
var oauth2 = [
|
|
1892
|
+
"github",
|
|
1893
|
+
"google",
|
|
1894
|
+
"microsoft",
|
|
1895
|
+
"discord",
|
|
1896
|
+
"facebook"
|
|
1897
|
+
];
|
|
1609
1898
|
function auth(app) {
|
|
1610
1899
|
app.use(async function middle(ctx) {
|
|
1611
1900
|
ctx.user = await getUser(ctx);
|
|
1612
1901
|
});
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
if (!
|
|
1902
|
+
const enabled = app.settings.auth.provider;
|
|
1903
|
+
for (const name of oauth2) {
|
|
1904
|
+
if (!enabled.includes(name)) continue;
|
|
1905
|
+
const key = name.toUpperCase();
|
|
1906
|
+
if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
|
|
1907
|
+
if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
|
|
1616
1908
|
app.get("/auth/logout", logout);
|
|
1617
|
-
app.get(
|
|
1618
|
-
app.get(
|
|
1909
|
+
app.get(`/auth/login/${name}`, providers_default[name].login);
|
|
1910
|
+
app.get(`/auth/callback/${name}`, providers_default[name].callback);
|
|
1619
1911
|
}
|
|
1620
|
-
if (
|
|
1912
|
+
if (enabled.includes("apple")) {
|
|
1913
|
+
const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
|
|
1914
|
+
for (const key of keys) {
|
|
1915
|
+
if (!env[key]) throw new Error(`${key} not defined`);
|
|
1916
|
+
}
|
|
1917
|
+
app.get("/auth/logout", logout);
|
|
1918
|
+
app.get("/auth/login/apple", providers_default.apple.login);
|
|
1919
|
+
app.post("/auth/callback/apple", providers_default.apple.callback);
|
|
1920
|
+
}
|
|
1921
|
+
if (enabled.includes("email")) {
|
|
1621
1922
|
app.post("/auth/logout", logout);
|
|
1622
1923
|
app.post("/auth/register/email", providers_default.email.register);
|
|
1623
1924
|
app.post("/auth/login/email", providers_default.email.login);
|
|
@@ -1650,7 +1951,7 @@ async function favicon(ctx) {
|
|
|
1650
1951
|
return icon ? type("ico").send(icon) : 204;
|
|
1651
1952
|
}
|
|
1652
1953
|
const handled = ctx.app.handlers.get.some(
|
|
1653
|
-
(
|
|
1954
|
+
(route) => pathPattern(route.path, "/favicon.ico")
|
|
1654
1955
|
);
|
|
1655
1956
|
if (handled) return;
|
|
1656
1957
|
return 204;
|
|
@@ -1669,11 +1970,8 @@ var encode = (str = "") => {
|
|
|
1669
1970
|
if (typeof str !== "string") return "";
|
|
1670
1971
|
return str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
1671
1972
|
};
|
|
1672
|
-
var getConfig = (
|
|
1673
|
-
const config2 =
|
|
1674
|
-
(r2) => typeof r2 !== "string" && typeof r2 !== "function" && typeof r2 === "object"
|
|
1675
|
-
);
|
|
1676
|
-
if (!config2) return {};
|
|
1973
|
+
var getConfig = (options = {}) => {
|
|
1974
|
+
const config2 = { ...options };
|
|
1677
1975
|
if (config2.tags) {
|
|
1678
1976
|
if (typeof config2.tags === "string") {
|
|
1679
1977
|
config2.tags = config2.tags.split(/\s*,\s*/g);
|
|
@@ -1718,13 +2016,10 @@ var generateOpenApiPaths = (handlers) => {
|
|
|
1718
2016
|
const paths = {};
|
|
1719
2017
|
for (const [method, routes] of Object.entries(handlers)) {
|
|
1720
2018
|
for (const route of routes) {
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
route.find((p) => typeof p === "object")
|
|
1726
|
-
];
|
|
1727
|
-
const config2 = getConfig(route);
|
|
2019
|
+
const path2 = route.path;
|
|
2020
|
+
const fn = route.fns.find((p) => typeof p === "function");
|
|
2021
|
+
const meta = route.fns.find((p) => typeof p === "object");
|
|
2022
|
+
const config2 = getConfig(route.options);
|
|
1728
2023
|
if (typeof path2 !== "string" || path2 === "*" || path2 === "/docs" || !fn) {
|
|
1729
2024
|
continue;
|
|
1730
2025
|
}
|
|
@@ -1823,7 +2118,7 @@ function preflight(ctx) {
|
|
|
1823
2118
|
if (ctx.method !== "options") return;
|
|
1824
2119
|
if (!ctx.headers["access-control-request-method"]) return;
|
|
1825
2120
|
const handled = ctx.app.handlers.options.some(
|
|
1826
|
-
(
|
|
2121
|
+
(route) => pathPattern(route.path, ctx.url.pathname)
|
|
1827
2122
|
);
|
|
1828
2123
|
if (handled) return;
|
|
1829
2124
|
return 204;
|
|
@@ -1885,9 +2180,9 @@ import { TLSSocket } from "tls";
|
|
|
1885
2180
|
// src/context/createEvents.ts
|
|
1886
2181
|
function createEvents() {
|
|
1887
2182
|
const events = {};
|
|
1888
|
-
events.on = (name,
|
|
2183
|
+
events.on = (name, callback3) => {
|
|
1889
2184
|
events[name] = events[name] || [];
|
|
1890
|
-
events[name].push(
|
|
2185
|
+
events[name].push(callback3);
|
|
1891
2186
|
};
|
|
1892
2187
|
events.trigger = (name, data) => {
|
|
1893
2188
|
if (!events[name]) return;
|
|
@@ -2008,7 +2303,7 @@ var Winter = async (app, request, env2) => {
|
|
|
2008
2303
|
if (env2?.upgrade(request)) return;
|
|
2009
2304
|
Object.assign(globalThis.env, env2);
|
|
2010
2305
|
const ctx = await createWinter(request, app, env2);
|
|
2011
|
-
const res = await handleRequest(app
|
|
2306
|
+
const res = await handleRequest(app, ctx);
|
|
2012
2307
|
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
2013
2308
|
return res;
|
|
2014
2309
|
};
|
|
@@ -2017,7 +2312,7 @@ var Node = async (app) => {
|
|
|
2017
2312
|
http.createServer(async (request, response) => {
|
|
2018
2313
|
const ctx = await createNode(request, app);
|
|
2019
2314
|
if ("error" in ctx) throw ctx.error;
|
|
2020
|
-
const out = await handleRequest(app
|
|
2315
|
+
const out = await handleRequest(app, ctx);
|
|
2021
2316
|
response.writeHead(out.status || 200, parseHeaders_default(out.headers));
|
|
2022
2317
|
if (out.body instanceof ReadableStream) {
|
|
2023
2318
|
await iterate(out.body, (chunk) => response.write(chunk));
|
|
@@ -2035,16 +2330,16 @@ var Netlify = async (app, request, context) => {
|
|
|
2035
2330
|
throw new Error("Netlify doesn't exist");
|
|
2036
2331
|
}
|
|
2037
2332
|
const ctx = await createWinter(request, app);
|
|
2038
|
-
const res = await handleRequest(app
|
|
2333
|
+
const res = await handleRequest(app, ctx);
|
|
2039
2334
|
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
2040
2335
|
return res;
|
|
2041
2336
|
};
|
|
2042
2337
|
|
|
2043
2338
|
// src/router.ts
|
|
2044
|
-
function isMiddleware(x) {
|
|
2045
|
-
return typeof x === "function";
|
|
2046
|
-
}
|
|
2047
2339
|
var Router = class _Router {
|
|
2340
|
+
// Cross-cutting middleware added with .use(); they run on every request
|
|
2341
|
+
middleware = [];
|
|
2342
|
+
// Routes per method, each carrying its own (already-flattened) chain of fns
|
|
2048
2343
|
handlers = {
|
|
2049
2344
|
socket: [],
|
|
2050
2345
|
get: [],
|
|
@@ -2060,79 +2355,67 @@ var Router = class _Router {
|
|
|
2060
2355
|
self() {
|
|
2061
2356
|
return this;
|
|
2062
2357
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2358
|
+
// Registers one route: bakes the current middleware + the route's own
|
|
2359
|
+
// functions into a single flat `fns` list. A plain options object may sit
|
|
2360
|
+
// between the path and the handlers, and it's pulled out here.
|
|
2361
|
+
handle(method, pathOrFn, ...rest) {
|
|
2362
|
+
let path2 = "*";
|
|
2363
|
+
if (typeof pathOrFn === "string") {
|
|
2364
|
+
path2 = pathOrFn;
|
|
2365
|
+
} else if (pathOrFn != null) {
|
|
2366
|
+
rest.unshift(pathOrFn);
|
|
2367
|
+
}
|
|
2368
|
+
let options = {};
|
|
2369
|
+
if (rest[0] != null && typeof rest[0] !== "function") {
|
|
2370
|
+
options = rest.shift();
|
|
2371
|
+
}
|
|
2372
|
+
const base = method === "socket" ? [] : this.middleware;
|
|
2373
|
+
const fns = [...base, ...rest].filter((fn) => fn != null);
|
|
2374
|
+
this.handlers[method].push({ path: path2, options, fns });
|
|
2072
2375
|
return this.self();
|
|
2073
2376
|
}
|
|
2074
2377
|
socket(pathOrMid, optionsOrMid, ...middleware) {
|
|
2075
|
-
|
|
2076
|
-
return this.handle("socket", pathOrMid, optionsOrMid, ...middleware);
|
|
2077
|
-
}
|
|
2078
|
-
return this.handle("socket", pathOrMid, ...middleware);
|
|
2378
|
+
return this.handle("socket", pathOrMid, optionsOrMid, ...middleware);
|
|
2079
2379
|
}
|
|
2080
2380
|
get(pathOrMid, optionsOrMid, ...middleware) {
|
|
2081
|
-
|
|
2082
|
-
return this.handle("get", pathOrMid, optionsOrMid, ...middleware);
|
|
2083
|
-
}
|
|
2084
|
-
return this.handle("get", pathOrMid, ...middleware);
|
|
2381
|
+
return this.handle("get", pathOrMid, optionsOrMid, ...middleware);
|
|
2085
2382
|
}
|
|
2086
2383
|
head(pathOrMid, optionsOrMid, ...middleware) {
|
|
2087
|
-
|
|
2088
|
-
return this.handle("head", pathOrMid, optionsOrMid, ...middleware);
|
|
2089
|
-
}
|
|
2090
|
-
return this.handle("head", pathOrMid, ...middleware);
|
|
2384
|
+
return this.handle("head", pathOrMid, optionsOrMid, ...middleware);
|
|
2091
2385
|
}
|
|
2092
2386
|
post(pathOrMid, optionsOrMid, ...middleware) {
|
|
2093
|
-
|
|
2094
|
-
return this.handle("post", pathOrMid, optionsOrMid, ...middleware);
|
|
2095
|
-
}
|
|
2096
|
-
return this.handle("post", pathOrMid, ...middleware);
|
|
2387
|
+
return this.handle("post", pathOrMid, optionsOrMid, ...middleware);
|
|
2097
2388
|
}
|
|
2098
2389
|
put(pathOrMid, optionsOrMid, ...middleware) {
|
|
2099
|
-
|
|
2100
|
-
return this.handle("put", pathOrMid, optionsOrMid, ...middleware);
|
|
2101
|
-
}
|
|
2102
|
-
return this.handle("put", pathOrMid, ...middleware);
|
|
2390
|
+
return this.handle("put", pathOrMid, optionsOrMid, ...middleware);
|
|
2103
2391
|
}
|
|
2104
2392
|
patch(pathOrMid, optionsOrMid, ...middleware) {
|
|
2105
|
-
|
|
2106
|
-
return this.handle("patch", pathOrMid, optionsOrMid, ...middleware);
|
|
2107
|
-
}
|
|
2108
|
-
return this.handle("patch", pathOrMid, ...middleware);
|
|
2393
|
+
return this.handle("patch", pathOrMid, optionsOrMid, ...middleware);
|
|
2109
2394
|
}
|
|
2110
2395
|
delete(pathOrMid, optionsOrMid, ...middleware) {
|
|
2111
|
-
|
|
2112
|
-
return this.handle("delete", pathOrMid, optionsOrMid, ...middleware);
|
|
2113
|
-
}
|
|
2114
|
-
return this.handle("delete", pathOrMid, ...middleware);
|
|
2396
|
+
return this.handle("delete", pathOrMid, optionsOrMid, ...middleware);
|
|
2115
2397
|
}
|
|
2116
2398
|
options(pathOrMid, optionsOrMid, ...middleware) {
|
|
2117
|
-
|
|
2118
|
-
return this.handle("options", pathOrMid, optionsOrMid, ...middleware);
|
|
2119
|
-
}
|
|
2120
|
-
return this.handle("options", pathOrMid, ...middleware);
|
|
2399
|
+
return this.handle("options", pathOrMid, optionsOrMid, ...middleware);
|
|
2121
2400
|
}
|
|
2122
2401
|
use(...args) {
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2402
|
+
for (const arg of args) {
|
|
2403
|
+
if (arg instanceof _Router) {
|
|
2404
|
+
for (const m of Object.keys(arg.handlers)) {
|
|
2405
|
+
for (const route of arg.handlers[m]) {
|
|
2406
|
+
const base = m === "socket" ? [] : this.middleware;
|
|
2407
|
+
this.handlers[m].push({
|
|
2408
|
+
path: route.path,
|
|
2409
|
+
options: route.options,
|
|
2410
|
+
fns: [...base, ...route.fns]
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2131
2413
|
}
|
|
2414
|
+
} else {
|
|
2415
|
+
this.middleware.push(arg);
|
|
2132
2416
|
}
|
|
2133
|
-
return this.self();
|
|
2134
2417
|
}
|
|
2135
|
-
return this.
|
|
2418
|
+
return this.self();
|
|
2136
2419
|
}
|
|
2137
2420
|
};
|
|
2138
2421
|
function router() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|