@server/next 0.25.10 → 0.27.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.js +2005 -0
- package/package.json +17 -13
- package/readme.md +1 -0
- package/src/{helpers/jsx.js → jsx/jsx-dev-runtime.js} +32 -17
- package/src/{helpers/jsx.test.jsx → jsx/jsx-runtime.test.jsx} +1 -1
- package/src/ServerError.js +0 -27
- package/src/auth/NoSession.js +0 -19
- package/src/auth/auth-cookie.test.js +0 -42
- package/src/auth/auth-token.test.js +0 -110
- package/src/auth/auth.js +0 -62
- package/src/auth/index.js +0 -104
- package/src/auth/index.test.js +0 -133
- package/src/auth/logout.js +0 -20
- package/src/auth/providers/email.js +0 -145
- package/src/auth/providers/github.js +0 -84
- package/src/auth/providers/index.js +0 -4
- package/src/auth/session.js +0 -18
- package/src/auth/updateUser.js +0 -6
- package/src/auth/user.js +0 -9
- package/src/context/node.js +0 -68
- package/src/context/parseBody.js +0 -107
- package/src/context/parseBody.test.js +0 -60
- package/src/context/parseCookies.js +0 -9
- package/src/context/winter.js +0 -46
- package/src/errors/index.js +0 -36
- package/src/helpers/StatusError.js +0 -6
- package/src/helpers/bucket.js +0 -89
- package/src/helpers/bucket.test.js +0 -51
- package/src/helpers/color.js +0 -30
- package/src/helpers/config.js +0 -64
- package/src/helpers/cookies.test.js +0 -23
- package/src/helpers/cors.js +0 -24
- package/src/helpers/cors.test.js +0 -64
- package/src/helpers/createCookies.js +0 -16
- package/src/helpers/createId.js +0 -51
- package/src/helpers/define.js +0 -18
- package/src/helpers/getMachine.js +0 -26
- package/src/helpers/handleRequest.js +0 -34
- package/src/helpers/index.js +0 -11
- package/src/helpers/iterate.js +0 -8
- package/src/helpers/parseHeaders.js +0 -15
- package/src/helpers/toWeb.js +0 -15
- package/src/helpers/types.js +0 -81
- package/src/helpers/validate.js +0 -34
- package/src/index.d.ts +0 -198
- package/src/index.js +0 -268
- package/src/index.test.js +0 -87
- package/src/index.types.ts +0 -28
- package/src/middle/assets.js +0 -17
- package/src/middle/assets.test.js +0 -10
- package/src/middle/index.js +0 -7
- package/src/middle/openapi.js +0 -147
- package/src/middle/timer.js +0 -14
- package/src/parseResponse.js +0 -111
- package/src/pathPattern.js +0 -47
- package/src/pathPattern.test.js +0 -99
- package/src/polyfill.js +0 -18
- package/src/reply.js +0 -153
- package/src/router.js +0 -53
- package/src/router.test.js +0 -57
- package/src/session.test.js +0 -65
- package/src/test/toSucceed.js +0 -64
- package/src/url.test.js +0 -26
package/src/auth/logout.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { cookies } from "../reply.js";
|
|
2
|
-
|
|
3
|
-
export default async function logout(ctx) {
|
|
4
|
-
const { id, type } = ctx.auth;
|
|
5
|
-
await ctx.options.auth.session.del(id);
|
|
6
|
-
|
|
7
|
-
if (type === "token") {
|
|
8
|
-
return { token: null };
|
|
9
|
-
}
|
|
10
|
-
if (type === "cookie") {
|
|
11
|
-
return cookies({ authorization: null }).redirect("/");
|
|
12
|
-
}
|
|
13
|
-
if (type === "jwt") {
|
|
14
|
-
throw new Error("JWT auth not supported yet");
|
|
15
|
-
}
|
|
16
|
-
if (type === "key") {
|
|
17
|
-
throw new Error("Key auth not supported yet");
|
|
18
|
-
}
|
|
19
|
-
throw new Error("Unknown auth type");
|
|
20
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { createId } from "../../helpers/index.js";
|
|
2
|
-
import { ServerError, status } from "../../index.js";
|
|
3
|
-
import updateUser from "../updateUser.js";
|
|
4
|
-
|
|
5
|
-
const hash = new Proxy(
|
|
6
|
-
{},
|
|
7
|
-
{
|
|
8
|
-
get: (self, key) => {
|
|
9
|
-
const load = async () =>
|
|
10
|
-
Object.assign(
|
|
11
|
-
self,
|
|
12
|
-
await import("argon2").catch(() => {
|
|
13
|
-
throw new ServerError.AUTH_ARGON_NEEDED();
|
|
14
|
-
}),
|
|
15
|
-
);
|
|
16
|
-
if (key === "verify" && !self.verify) {
|
|
17
|
-
return async (hash, pass) => {
|
|
18
|
-
await load();
|
|
19
|
-
return self.verify(hash, pass);
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
if (key === "hash" && !self.hash) {
|
|
23
|
-
return async (pass) => {
|
|
24
|
-
await load();
|
|
25
|
-
return self.hash(pass);
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return self[key];
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const createSession = async (user, ctx) => {
|
|
34
|
-
const { type, session, cleanUser, redirect = "/user" } = ctx.options.auth;
|
|
35
|
-
user = cleanUser(user);
|
|
36
|
-
const id = createId();
|
|
37
|
-
const provider = "email";
|
|
38
|
-
const time = new Date().toISOString().replace(/\.[0-9]*/, "");
|
|
39
|
-
ctx.auth = {
|
|
40
|
-
id,
|
|
41
|
-
type,
|
|
42
|
-
provider,
|
|
43
|
-
user: user.email,
|
|
44
|
-
email: user.email,
|
|
45
|
-
time,
|
|
46
|
-
};
|
|
47
|
-
await session.set(id, ctx.auth, { expires: "1w" });
|
|
48
|
-
|
|
49
|
-
if (type === "token") {
|
|
50
|
-
return status(201).json({ ...user, token: id });
|
|
51
|
-
}
|
|
52
|
-
if (type === "cookie") {
|
|
53
|
-
return status(302).cookies({ authentication: id }).redirect(redirect);
|
|
54
|
-
}
|
|
55
|
-
if (type === "jwt") {
|
|
56
|
-
throw new Error("JWT auth not supported yet");
|
|
57
|
-
}
|
|
58
|
-
if (type === "key") {
|
|
59
|
-
throw new Error("Key auth not supported yet");
|
|
60
|
-
}
|
|
61
|
-
throw new Error("Unknown auth type");
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
async function emailLogin(ctx) {
|
|
65
|
-
const { email, password } = ctx.body;
|
|
66
|
-
if (!email) throw ServerError.LOGIN_NO_EMAIL();
|
|
67
|
-
if (!/@/.test(email)) throw ServerError.LOGIN_INVALID_EMAIL();
|
|
68
|
-
if (!password) throw ServerError.LOGIN_NO_PASSWORD();
|
|
69
|
-
if (password.length < 8) throw ServerError.LOGIN_INVALID_PASSWORD();
|
|
70
|
-
|
|
71
|
-
const store = ctx.options.auth.store;
|
|
72
|
-
if (!(await store.has(email))) throw ServerError.LOGIN_WRONG_EMAIL();
|
|
73
|
-
|
|
74
|
-
const user = await store.get(email);
|
|
75
|
-
const isValid = await hash.verify(user.password, password);
|
|
76
|
-
if (!isValid) throw ServerError.LOGIN_WRONG_PASSWORD();
|
|
77
|
-
|
|
78
|
-
return createSession(user, ctx);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function emailRegister(ctx) {
|
|
82
|
-
const { email, password, ...data } = ctx.body;
|
|
83
|
-
if (!email) throw ServerError.REGISTER_NO_EMAIL();
|
|
84
|
-
if (!/@/.test(email)) throw ServerError.REGISTER_INVALID_EMAIL();
|
|
85
|
-
if (!password) throw ServerError.REGISTER_NO_PASSWORD();
|
|
86
|
-
if (password.length < 8) throw ServerError.REGISTER_INVALID_PASSWORD();
|
|
87
|
-
|
|
88
|
-
const store = ctx.options.auth.store;
|
|
89
|
-
if (await store.has(email)) throw ServerError.REGISTER_EMAIL_EXISTS();
|
|
90
|
-
|
|
91
|
-
const time = new Date().toISOString().replace(/\.[0-9]*/, "");
|
|
92
|
-
const user = {
|
|
93
|
-
id: createId(email),
|
|
94
|
-
email,
|
|
95
|
-
password: await hash.hash(password),
|
|
96
|
-
time,
|
|
97
|
-
...data,
|
|
98
|
-
};
|
|
99
|
-
await store.set(email, user);
|
|
100
|
-
|
|
101
|
-
return createSession(user, ctx);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function emailResetPassword(ctx) {
|
|
105
|
-
// const reset = ctx.options.store.prefix("reset:");
|
|
106
|
-
// // Already resetting
|
|
107
|
-
// if (ctx.body.token) {
|
|
108
|
-
// const { token, password } = ctx.body;
|
|
109
|
-
// const secret = sha256(token);
|
|
110
|
-
// const auth = await reset.get(secret);
|
|
111
|
-
// const user = await store.get(auth.email);
|
|
112
|
-
// } else {
|
|
113
|
-
// const email = ctx.body.email;
|
|
114
|
-
// const user = await store.get(email);
|
|
115
|
-
// const token = createId();
|
|
116
|
-
// const secret = sha256(token);
|
|
117
|
-
// await reset.set(secret, { email }, { expires: "2h" });
|
|
118
|
-
// //
|
|
119
|
-
// ctx.email.send(
|
|
120
|
-
// user.email,
|
|
121
|
-
// `Reset email: <a href="${domain}/auth/reset/email?token=${token}">`
|
|
122
|
-
// );
|
|
123
|
-
// }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function emailUpdatePassword(ctx) {
|
|
127
|
-
const { previous, updated } = ctx.body;
|
|
128
|
-
|
|
129
|
-
const fullUser = await ctx.options.auth.store.get(ctx.auth.user);
|
|
130
|
-
|
|
131
|
-
const isValid = await hash.verify(fullUser.password, previous);
|
|
132
|
-
if (!isValid) throw ServerError.LOGIN_WRONG_PASSWORD();
|
|
133
|
-
|
|
134
|
-
fullUser.password = await hash.hash(updated);
|
|
135
|
-
await updateUser(fullUser, ctx.auth, ctx.options.auth.store);
|
|
136
|
-
|
|
137
|
-
return 200;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export default {
|
|
141
|
-
login: emailLogin,
|
|
142
|
-
register: emailRegister,
|
|
143
|
-
reset: emailResetPassword,
|
|
144
|
-
password: emailUpdatePassword,
|
|
145
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import createId from "../../helpers/createId.js";
|
|
2
|
-
import { redirect, status } from "../../reply.js";
|
|
3
|
-
|
|
4
|
-
const oauth = async (code) => {
|
|
5
|
-
const fch = async (url, { body, headers = {}, ...rest } = {}) => {
|
|
6
|
-
headers.accept = "application/json";
|
|
7
|
-
headers["content-type"] = "application/json";
|
|
8
|
-
const res = await fetch(url, { ...rest, body, headers });
|
|
9
|
-
if (!res.ok) throw new Error("Invalid request");
|
|
10
|
-
return res.json();
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const res = await fch("https://github.com/login/oauth/access_token", {
|
|
14
|
-
method: "post",
|
|
15
|
-
body: JSON.stringify({
|
|
16
|
-
client_id: env.GITHUB_ID,
|
|
17
|
-
client_secret: env.GITHUB_SECRET,
|
|
18
|
-
code,
|
|
19
|
-
}),
|
|
20
|
-
});
|
|
21
|
-
return (path) => {
|
|
22
|
-
return fch(`https://api.github.com${path}`, {
|
|
23
|
-
headers: { Authorization: `Bearer ${res.access_token}` },
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const login = function githubLogin(ctx) {
|
|
29
|
-
return redirect(
|
|
30
|
-
`https://github.com/login/oauth/authorize?client_id=${env.GITHUB_ID}&scope=user:email`,
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const getUserProfile = async (code) => {
|
|
35
|
-
const api = await oauth(code);
|
|
36
|
-
const [profile, emails] = await Promise.all([
|
|
37
|
-
api("/user"),
|
|
38
|
-
api("/user/emails"),
|
|
39
|
-
]);
|
|
40
|
-
const email = emails.sort((a) => (a.primary ? -1 : 1))[0]?.email;
|
|
41
|
-
return { ...profile, email };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const callback = async (ctx) => {
|
|
45
|
-
const { type, cleanUser, store, session, redirect } = ctx.options.auth;
|
|
46
|
-
|
|
47
|
-
const profile = await getUserProfile(ctx.url.query.code);
|
|
48
|
-
|
|
49
|
-
const auth = {
|
|
50
|
-
id: createId(),
|
|
51
|
-
type,
|
|
52
|
-
provider: "github",
|
|
53
|
-
user: createId(profile.email),
|
|
54
|
-
email: profile.email,
|
|
55
|
-
time: new Date().toISOString().replace(/\.[0-9]*/, ""),
|
|
56
|
-
};
|
|
57
|
-
const user = cleanUser({
|
|
58
|
-
id: profile.id,
|
|
59
|
-
name: profile.name,
|
|
60
|
-
email: profile.email,
|
|
61
|
-
picture: profile.avatar_url,
|
|
62
|
-
location: profile.location,
|
|
63
|
-
created: profile.created_at,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
await store.set(auth.user, user);
|
|
67
|
-
await session.set(auth.id, auth, { expires: "1w" });
|
|
68
|
-
|
|
69
|
-
if (auth.type === "token") {
|
|
70
|
-
return status(201).json({ ...user, token: auth.id });
|
|
71
|
-
}
|
|
72
|
-
if (auth.type === "cookie") {
|
|
73
|
-
return status(302).cookies({ authentication: auth.id }).redirect(redirect);
|
|
74
|
-
}
|
|
75
|
-
if (auth.type === "jwt") {
|
|
76
|
-
throw new Error("JWT auth not supported yet");
|
|
77
|
-
}
|
|
78
|
-
if (auth.type === "key") {
|
|
79
|
-
throw new Error("Key auth not supported yet");
|
|
80
|
-
}
|
|
81
|
-
throw new Error("Unknown auth type");
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export default { login, callback };
|
package/src/auth/session.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import NoSession from "./NoSession.js";
|
|
2
|
-
|
|
3
|
-
export default async function session(ctx) {
|
|
4
|
-
const store = ctx.options.session?.store;
|
|
5
|
-
|
|
6
|
-
// If there's no store at all, we don't have session available;
|
|
7
|
-
// but that's okay, since it's only a problem if you try to use it
|
|
8
|
-
if (!store) return NoSession;
|
|
9
|
-
|
|
10
|
-
// There's a session cookie; use it as the key to get the data
|
|
11
|
-
// from the store
|
|
12
|
-
if (ctx.cookies.session) {
|
|
13
|
-
const session = await store.get(ctx.cookies.session);
|
|
14
|
-
if (session) return session;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {};
|
|
18
|
-
}
|
package/src/auth/updateUser.js
DELETED
package/src/auth/user.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import ServerError from "../ServerError.js";
|
|
2
|
-
|
|
3
|
-
export default async function user(ctx) {
|
|
4
|
-
if (!ctx.auth) return;
|
|
5
|
-
|
|
6
|
-
const user = await ctx.options.auth.store.get(ctx.auth.user);
|
|
7
|
-
if (!user) throw ServerError.AUTH_NO_USER();
|
|
8
|
-
return ctx.options.auth.cleanUser(user);
|
|
9
|
-
}
|
package/src/context/node.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import auth from "../auth/index.js";
|
|
2
|
-
import { define, parseHeaders } from "../helpers/index.js";
|
|
3
|
-
import parseBody from "./parseBody.js";
|
|
4
|
-
import parseCookies from "./parseCookies.js";
|
|
5
|
-
|
|
6
|
-
// Headers come like [title1, value1, title2, value2, ...]
|
|
7
|
-
// https://stackoverflow.com/a/54029307/938236
|
|
8
|
-
const chunkArray = (arr, size) =>
|
|
9
|
-
arr.length > size
|
|
10
|
-
? [arr.slice(0, size), ...chunkArray(arr.slice(size), size)]
|
|
11
|
-
: [arr];
|
|
12
|
-
|
|
13
|
-
export default async (request, app) => {
|
|
14
|
-
const ctx = {};
|
|
15
|
-
ctx.options = app.opts || {};
|
|
16
|
-
ctx.req = request;
|
|
17
|
-
ctx.res = { status: null, headers: {}, cookies: {} };
|
|
18
|
-
ctx.method = request.method.toLowerCase();
|
|
19
|
-
|
|
20
|
-
// Private
|
|
21
|
-
const events = {};
|
|
22
|
-
ctx.unstableOn = (name, callback) => {
|
|
23
|
-
events[name] = events[name] || [];
|
|
24
|
-
events[name].push(callback);
|
|
25
|
-
};
|
|
26
|
-
ctx.unstableFire = (name, data) => {
|
|
27
|
-
if (!events[name]) return;
|
|
28
|
-
for (const cb of events[name]) {
|
|
29
|
-
cb(data);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
ctx.headers = parseHeaders(new Headers(chunkArray(request.rawHeaders, 2)));
|
|
34
|
-
ctx.cookies = parseCookies(ctx.headers.cookie);
|
|
35
|
-
await auth.load(ctx);
|
|
36
|
-
|
|
37
|
-
const https = request.connection.encrypted ? "https" : "http";
|
|
38
|
-
const host = ctx.headers.host || `localhost:${ctx.options.port}`;
|
|
39
|
-
const path = request.url.replace(/\/$/, "") || "/";
|
|
40
|
-
ctx.url = new URL(path, `${https}://${host}`);
|
|
41
|
-
define(ctx.url, "query", (url) =>
|
|
42
|
-
Object.fromEntries(url.searchParams.entries()),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
await new Promise((resolve, reject) => {
|
|
46
|
-
const body = [];
|
|
47
|
-
request
|
|
48
|
-
.on("data", (chunk) => {
|
|
49
|
-
body.push(chunk);
|
|
50
|
-
})
|
|
51
|
-
.on("end", async () => {
|
|
52
|
-
const type = ctx.headers["content-type"];
|
|
53
|
-
ctx.body = await parseBody(
|
|
54
|
-
Buffer.concat(body).toString(),
|
|
55
|
-
type,
|
|
56
|
-
ctx.options.uploads,
|
|
57
|
-
);
|
|
58
|
-
resolve();
|
|
59
|
-
})
|
|
60
|
-
.on("error", reject);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
ctx.app = app;
|
|
64
|
-
ctx.platform = app.platform;
|
|
65
|
-
ctx.machine = app.platform;
|
|
66
|
-
|
|
67
|
-
return ctx;
|
|
68
|
-
};
|
package/src/context/parseBody.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { createId } from "../helpers/index.js";
|
|
2
|
-
|
|
3
|
-
function getBoundary(header) {
|
|
4
|
-
if (!header) return null;
|
|
5
|
-
|
|
6
|
-
// When we set the `content-type` manually on fetch(), it won't include the
|
|
7
|
-
// boundary and it's recommended not to set it manually:
|
|
8
|
-
// https://stackoverflow.com/q/39280438/938236
|
|
9
|
-
if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
|
|
10
|
-
console.error("Do not set the `Content-Type` manually for FormData");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const items = header.split(";");
|
|
14
|
-
for (const item of items) {
|
|
15
|
-
const trimmedItem = item.trim();
|
|
16
|
-
if (trimmedItem.startsWith("boundary=")) {
|
|
17
|
-
return trimmedItem.split("=")[1].trim();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getMatching(string, regex) {
|
|
24
|
-
const matches = string.match(regex);
|
|
25
|
-
return matches?.[1] ? matches[1] : "";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const saveFile = async (name, value, bucket) => {
|
|
29
|
-
const ext = name.split(".").pop();
|
|
30
|
-
const id = `${createId()}.${ext}`;
|
|
31
|
-
await bucket.write(id, value, "binary");
|
|
32
|
-
return id;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Utility function to split a buffer
|
|
36
|
-
function splitBuffer(buffer, delimiter) {
|
|
37
|
-
const result = [];
|
|
38
|
-
let start = 0;
|
|
39
|
-
let index = buffer.indexOf(delimiter, start);
|
|
40
|
-
|
|
41
|
-
while (index !== -1) {
|
|
42
|
-
result.push(buffer.slice(start, index));
|
|
43
|
-
start = index + delimiter.length;
|
|
44
|
-
index = buffer.indexOf(delimiter, start);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
result.push(buffer.slice(start));
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const BREAK = "\r\n\r\n";
|
|
52
|
-
|
|
53
|
-
export default async function parseBody(raw, contentType, bucket) {
|
|
54
|
-
const baseBuffer = typeof raw === "string" ? raw : await raw.arrayBuffer();
|
|
55
|
-
const rawBuffer = Buffer.from(baseBuffer);
|
|
56
|
-
if (!rawBuffer) return {};
|
|
57
|
-
|
|
58
|
-
if (!contentType || /text\/plain/.test(contentType)) {
|
|
59
|
-
return rawBuffer.toString(); // Return as plain text
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (/application\/json/.test(contentType)) {
|
|
63
|
-
return JSON.parse(rawBuffer.toString()); // Parse JSON
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const boundary = getBoundary(contentType);
|
|
67
|
-
if (!boundary) return null;
|
|
68
|
-
|
|
69
|
-
const body = {};
|
|
70
|
-
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
71
|
-
const parts = splitBuffer(rawBuffer, boundaryBuffer);
|
|
72
|
-
|
|
73
|
-
for (const part of parts) {
|
|
74
|
-
if (part.length === 0 || part.equals(Buffer.from("--\r\n"))) continue;
|
|
75
|
-
|
|
76
|
-
const partString = part.toString();
|
|
77
|
-
const name = getMatching(partString, /(?:name=")(.+?)(?:")/)
|
|
78
|
-
.trim()
|
|
79
|
-
.replace(/\[\]$/, "");
|
|
80
|
-
if (!name) continue;
|
|
81
|
-
|
|
82
|
-
const filename = getMatching(partString, /(?:filename=")(.*?)(?:")/).trim();
|
|
83
|
-
|
|
84
|
-
if (!part.includes(BREAK)) continue;
|
|
85
|
-
|
|
86
|
-
// Content starts after headers and "\r\n\r\n", remove trailing CRLF
|
|
87
|
-
const content = part.slice(part.indexOf(BREAK) + 4, part.length - 2);
|
|
88
|
-
|
|
89
|
-
if (filename) {
|
|
90
|
-
// Save binary content as a file
|
|
91
|
-
body[name] = await saveFile(filename, content, bucket);
|
|
92
|
-
} else {
|
|
93
|
-
// Treat content as text
|
|
94
|
-
const value = content.toString().trim();
|
|
95
|
-
if (body[name]) {
|
|
96
|
-
if (!Array.isArray(body[name])) {
|
|
97
|
-
body[name] = [body[name]];
|
|
98
|
-
}
|
|
99
|
-
body[name].push(value);
|
|
100
|
-
} else {
|
|
101
|
-
body[name] = value;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return body;
|
|
107
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import parseBody from "./parseBody.js";
|
|
2
|
-
|
|
3
|
-
const getBody = () => {
|
|
4
|
-
let body = "trash1\r\n";
|
|
5
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
6
|
-
body += 'Content-Disposition: form-data; name="hello";\r\n\r\n';
|
|
7
|
-
body += "world\r\n";
|
|
8
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
9
|
-
body +=
|
|
10
|
-
'Content-Disposition: form-data; name="profile"; filename="profile.md"\r\n';
|
|
11
|
-
body += "Content-Type: text/plain\r\n\r\n";
|
|
12
|
-
body += "@11X";
|
|
13
|
-
body += "111Y\r\n";
|
|
14
|
-
body += "111Z\rCCCC\nCCCC\r\nCCCCC@\r\n\r\n";
|
|
15
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
16
|
-
body +=
|
|
17
|
-
'Content-Disposition: form-data; name="gallery[]"; filename="A.txt"\r\n';
|
|
18
|
-
body += "Content-Type: text/plain\r\n\r\n";
|
|
19
|
-
body += "@11X";
|
|
20
|
-
body += "111Y\r\n";
|
|
21
|
-
body += "111Z\rCCCC\nCCCC\r\nCCCCC@\r\n\r\n";
|
|
22
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
23
|
-
body += 'Content-Disposition: form-data; name="test";\r\n\r\n';
|
|
24
|
-
body += "test message 123456\r\n";
|
|
25
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
26
|
-
body += 'Content-Disposition: form-data; name="test";\r\n\r\n';
|
|
27
|
-
body += "test message number two\r\n";
|
|
28
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp\r\n";
|
|
29
|
-
body +=
|
|
30
|
-
'Content-Disposition: form-data; name="gallery[]"; filename="C.txt"\r\n';
|
|
31
|
-
body += "Content-Type: text/plain\r\n\r\n";
|
|
32
|
-
body += "@CCC";
|
|
33
|
-
body += "CCCY\r\n";
|
|
34
|
-
body += "CCCZ\rCCCW\nCCC0\r\n666@\r\n";
|
|
35
|
-
body += "------WebKitFormBoundaryvef1fLxmoUdYZWXp--\r\n";
|
|
36
|
-
return body;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
describe("parseBody", () => {
|
|
40
|
-
it("can parse the example body", async () => {
|
|
41
|
-
const body = await parseBody(
|
|
42
|
-
getBody(),
|
|
43
|
-
"multipart/form-data; boundary=----WebKitFormBoundaryvef1fLxmoUdYZWXp",
|
|
44
|
-
{ write: (id) => id },
|
|
45
|
-
);
|
|
46
|
-
expect(body).toMatchObject({
|
|
47
|
-
hello: "world",
|
|
48
|
-
test: ["test message 123456", "test message number two"],
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const matchMd = expect.stringMatching(/^\w{16}.md$/);
|
|
52
|
-
const matchTxt = expect.stringMatching(/^\w{16}.txt$/);
|
|
53
|
-
expect(body).toMatchObject({
|
|
54
|
-
hello: "world",
|
|
55
|
-
profile: matchMd,
|
|
56
|
-
gallery: matchTxt,
|
|
57
|
-
test: ["test message 123456", "test message number two"],
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
package/src/context/winter.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import auth from "../auth/index.js";
|
|
2
|
-
import { define, parseHeaders } from "../helpers/index.js";
|
|
3
|
-
import parseBody from "./parseBody.js";
|
|
4
|
-
import parseCookies from "./parseCookies.js";
|
|
5
|
-
|
|
6
|
-
export default async (request, app) => {
|
|
7
|
-
const ctx = {};
|
|
8
|
-
ctx.init = performance.now();
|
|
9
|
-
ctx.options = app.opts || {};
|
|
10
|
-
ctx.req = request;
|
|
11
|
-
ctx.res = { status: null, headers: {}, cookies: {} };
|
|
12
|
-
ctx.method = request.method.toLowerCase();
|
|
13
|
-
|
|
14
|
-
// Private
|
|
15
|
-
const events = {};
|
|
16
|
-
ctx.unstableOn = (name, callback) => {
|
|
17
|
-
events[name] = events[name] || [];
|
|
18
|
-
events[name].push(callback);
|
|
19
|
-
};
|
|
20
|
-
ctx.unstableFire = (name, data) => {
|
|
21
|
-
if (!events[name]) return;
|
|
22
|
-
for (const cb of events[name]) {
|
|
23
|
-
cb(data);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
ctx.headers = parseHeaders(request.headers);
|
|
28
|
-
ctx.cookies = parseCookies(ctx.headers.cookie);
|
|
29
|
-
await auth.load(ctx);
|
|
30
|
-
|
|
31
|
-
ctx.url = new URL(request.url.replace(/\/$/, ""));
|
|
32
|
-
define(ctx.url, "query", (url) =>
|
|
33
|
-
Object.fromEntries(url.searchParams.entries()),
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
if (request.body) {
|
|
37
|
-
const type = ctx.headers["content-type"];
|
|
38
|
-
ctx.body = await parseBody(request, type, ctx.options.uploads);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
ctx.app = app;
|
|
42
|
-
ctx.platform = app.platform;
|
|
43
|
-
ctx.machine = app.platform;
|
|
44
|
-
|
|
45
|
-
return ctx;
|
|
46
|
-
};
|
package/src/errors/index.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import ServerError from "../ServerError.js";
|
|
2
|
-
|
|
3
|
-
ServerError.extend({
|
|
4
|
-
NO_STORE: "You need a 'store' to write 'ctx.session'",
|
|
5
|
-
NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
|
|
6
|
-
NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
|
|
7
|
-
|
|
8
|
-
AUTH_ARGON_NEEDED:
|
|
9
|
-
"Argon2 is needed for the auth module, please install it with 'npm i argon2'",
|
|
10
|
-
AUTH_INVALID_TYPE: "Invalid Authorization type, '{type}'",
|
|
11
|
-
AUTH_INVALID_TOKEN: "Invalid Authorization token",
|
|
12
|
-
AUTH_INVALID_COOKIE: "Invalid Authorization cookie",
|
|
13
|
-
AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
|
|
14
|
-
AUTH_INVALID_PROVIDER:
|
|
15
|
-
"Invalid provider '{provider}', valid ones are: '{valid}'",
|
|
16
|
-
AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
|
|
17
|
-
AUTH_NO_USER: {
|
|
18
|
-
status: 401,
|
|
19
|
-
message: "Credentials do not correspond to a user",
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
LOGIN_NO_EMAIL: "The email is required to log in",
|
|
23
|
-
LOGIN_INVALID_EMAIL: "The email you wrote is not correct",
|
|
24
|
-
LOGIN_NO_PASSWORD: "The email is required to log in",
|
|
25
|
-
LOGIN_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
26
|
-
LOGIN_WRONG_ACCOUNT: "That email does not correspond to any account",
|
|
27
|
-
LOGIN_WRONG_PASSWORD: "That is not the valid password",
|
|
28
|
-
|
|
29
|
-
REGISTER_NO_EMAIL: "Email needed",
|
|
30
|
-
REGISTER_INVALID_EMAIL: "The email you wrote is not correct",
|
|
31
|
-
REGISTER_NO_PASSWORD: "Password needed",
|
|
32
|
-
REGISTER_INVALID_PASSWORD: "The password you wrote is not correct",
|
|
33
|
-
REGISTER_EMAIL_EXISTS: "Email is already registered",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export default ServerError;
|