@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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": "https://github.com/franciscop/server-next.git",
|
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
"title": "Server JS - A modern web server for Bun and Node.js",
|
|
13
13
|
"home": "./docs/index.html",
|
|
14
14
|
"menu": {
|
|
15
|
-
"About": "/",
|
|
16
15
|
"Documentation": "/documentation",
|
|
17
16
|
"Github": "https://github.com/franciscop/server-next"
|
|
18
17
|
}
|
|
19
18
|
},
|
|
20
19
|
"scripts": {
|
|
20
|
+
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24",
|
|
21
21
|
"start": "bun test --watch",
|
|
22
22
|
"lint": "npx @biomejs/biome lint ./src --skip=lint/style/noParameterAssign --skip=lint/suspicious/noExplicitAny --skip=lint/suspicious/noConfusingVoidType",
|
|
23
|
-
"test": "
|
|
23
|
+
"test": "npm run test:bun && tsc --noEmit",
|
|
24
|
+
"test:bun": "bun test",
|
|
24
25
|
"test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
25
26
|
},
|
|
26
27
|
"keywords": [
|
|
@@ -29,25 +30,28 @@
|
|
|
29
30
|
"server.js"
|
|
30
31
|
],
|
|
31
32
|
"type": "module",
|
|
32
|
-
"main": "
|
|
33
|
-
"types": "
|
|
33
|
+
"main": "index.js",
|
|
34
|
+
"types": "index.d.ts",
|
|
34
35
|
"files": [
|
|
35
|
-
"src/"
|
|
36
|
-
"jsx-dev-runtime.js"
|
|
36
|
+
"src/jsx/"
|
|
37
37
|
],
|
|
38
38
|
"exports": {
|
|
39
|
-
".": "./
|
|
40
|
-
"./jsx-runtime": "./src/
|
|
41
|
-
"./jsx-dev-runtime": "./src/
|
|
39
|
+
".": "./index.js",
|
|
40
|
+
"./jsx-runtime": "./src/jsx/jsx-dev-runtime.js",
|
|
41
|
+
"./jsx-dev-runtime": "./src/jsx/jsx-dev-runtime.js"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"
|
|
44
|
+
"@types/bun": "^1.3.0",
|
|
45
|
+
"@types/jest": "^30.0.0",
|
|
46
|
+
"@types/node": "^24.10.0",
|
|
45
47
|
"check-dts": "^0.8.2",
|
|
46
48
|
"jest": "^29.7.0",
|
|
47
|
-
"polystore": "^0.14.1"
|
|
49
|
+
"polystore": "^0.14.1",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
48
52
|
},
|
|
49
53
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
54
|
+
"node": ">=24.11.0"
|
|
51
55
|
},
|
|
52
56
|
"engineStrict": true,
|
|
53
57
|
"jest": {
|
package/readme.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Server JS
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
const entities = {
|
|
1
|
+
const entities = {
|
|
2
|
+
"&": "&",
|
|
3
|
+
"<": "<",
|
|
4
|
+
">": ">",
|
|
5
|
+
'"': """,
|
|
6
|
+
};
|
|
7
|
+
|
|
2
8
|
const encode = (str = "") => {
|
|
3
9
|
if (typeof str === "number") str = String(str);
|
|
4
|
-
if (typeof str !== "string") return "";
|
|
10
|
+
if (typeof str !== "string") return "";
|
|
5
11
|
return str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
6
12
|
};
|
|
7
13
|
|
|
@@ -17,60 +23,69 @@ const altAttrs = {
|
|
|
17
23
|
const isValidChild = (child) => child || child === "" || child === 0;
|
|
18
24
|
|
|
19
25
|
const escapeCSS = (value) => String(value).replace(/[<>&"'`]/g, "\\$&");
|
|
26
|
+
|
|
20
27
|
const minifyCss = (str) =>
|
|
21
28
|
str
|
|
22
29
|
.replace(/\s+/g, " ")
|
|
23
|
-
.replace(/(
|
|
30
|
+
.replace(/(?!<")\/\*[^*]+\*\/(?!")/g, "")
|
|
24
31
|
.replace(/(\w|\*) (\{)/g, "$1$2")
|
|
25
32
|
.replace(/(\}) (\w|\*)/g, "$1$2")
|
|
26
33
|
.replace(/(\{) (\w)/g, "$1$2")
|
|
27
|
-
.replace(/(\w)(
|
|
28
|
-
.replace(/(
|
|
29
|
-
.replace(/(
|
|
30
|
-
.replace(
|
|
34
|
+
.replace(/(\w)(:) /g, "$1$2")
|
|
35
|
+
.replace(/(;) (\})/g, "$1$2")
|
|
36
|
+
.replace(/(;) (\w)/g, "$1$2")
|
|
37
|
+
.replace(/;(\})/g, "$1")
|
|
31
38
|
.replace(/(\w), (\w)/g, "$1,$2")
|
|
32
39
|
.replace(/(\w), (\w)/g, "$1,$2")
|
|
33
40
|
.replace(/(\{) (\w)/g, "$1$2")
|
|
34
41
|
.trim();
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
const jsx = (tag, { children, ...props }) => {
|
|
37
44
|
if (typeof tag === "function") return tag({ children, ...props });
|
|
38
45
|
|
|
39
|
-
// If they include an explicit <script>, let them be, just render it
|
|
40
|
-
// without escaping
|
|
41
46
|
if (tag === "script" && children) {
|
|
42
47
|
const src = children;
|
|
43
48
|
children = () => src;
|
|
44
49
|
}
|
|
50
|
+
|
|
45
51
|
if (tag === "style" && children && typeof children === "string") {
|
|
46
52
|
const src = minifyCss(escapeCSS(children));
|
|
47
53
|
children = () => src;
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
|
|
56
|
+
if (props?.dangerouslySetInnerHTML) {
|
|
50
57
|
children = () => props.dangerouslySetInnerHTML.__html;
|
|
58
|
+
}
|
|
51
59
|
|
|
52
60
|
if (!isValidChild(children)) children = [];
|
|
53
61
|
if (typeof children === "string") children = [children];
|
|
62
|
+
|
|
54
63
|
children = (Array.isArray(children) ? children : [children])
|
|
55
64
|
.flat()
|
|
56
65
|
.map((c) => (typeof c === "function" ? c() : encode(c)))
|
|
57
66
|
.join("");
|
|
67
|
+
|
|
58
68
|
if (!tag) return () => children;
|
|
69
|
+
|
|
59
70
|
let attrStr = Object.entries(props || {})
|
|
60
|
-
.filter(([k
|
|
71
|
+
.filter(([k]) => k !== "dangerouslySetInnerHTML")
|
|
61
72
|
.filter(([k, v]) => !/on[A-Z]/.test(k) && typeof v !== "function")
|
|
62
|
-
.filter(([
|
|
73
|
+
.filter(([, v]) => v !== false)
|
|
63
74
|
.map(([k, v]) =>
|
|
64
75
|
v === true
|
|
65
76
|
? altAttrs[k.toLowerCase()] || encode(k)
|
|
66
|
-
: `${altAttrs[k.toLowerCase()] || encode(k)}="${encode(v)}"`,
|
|
77
|
+
: `${altAttrs[k.toLowerCase()] || encode(k)}="${encode(String(v))}"`,
|
|
67
78
|
)
|
|
68
79
|
.join(" ");
|
|
80
|
+
|
|
69
81
|
if (attrStr) attrStr = ` ${attrStr}`;
|
|
82
|
+
|
|
70
83
|
if (SELFCLOSE.has(tag)) return () => `<${tag}${attrStr} />`;
|
|
84
|
+
|
|
71
85
|
const doctype = tag === "html" ? "<!DOCTYPE html>" : "";
|
|
72
86
|
return () => `${doctype}<${tag}${attrStr}>${children}</${tag}>`;
|
|
73
87
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
|
|
89
|
+
const Fragment = "";
|
|
90
|
+
|
|
91
|
+
export { jsx, jsx as jsxs, jsx as jsxDEV, Fragment };
|
package/src/ServerError.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export default class ServerError extends Error {
|
|
2
|
-
constructor(code, status, message, vars = {}) {
|
|
3
|
-
if (typeof message === "function") {
|
|
4
|
-
message = message(vars);
|
|
5
|
-
}
|
|
6
|
-
if (typeof message !== "string") throw Error(`Invalid error ${message}`);
|
|
7
|
-
for (const key in vars) {
|
|
8
|
-
let val = vars[key];
|
|
9
|
-
if (Array.isArray(val)) val = vars[key].join(",");
|
|
10
|
-
message = message.replaceAll(`{${key}}`, val);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
super(message);
|
|
14
|
-
this.code = code;
|
|
15
|
-
this.message = message;
|
|
16
|
-
this.status = status;
|
|
17
|
-
}
|
|
18
|
-
static extend(errors) {
|
|
19
|
-
for (const code in errors) {
|
|
20
|
-
const message = errors[code]?.message || errors[code];
|
|
21
|
-
const status = errors[code]?.status;
|
|
22
|
-
ServerError[code] = (vars) =>
|
|
23
|
-
new ServerError(code, status, message, vars);
|
|
24
|
-
}
|
|
25
|
-
return errors;
|
|
26
|
-
}
|
|
27
|
-
}
|
package/src/auth/NoSession.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { ServerError } from "../index.js";
|
|
2
|
-
|
|
3
|
-
class NoSession {}
|
|
4
|
-
|
|
5
|
-
export default new Proxy(NoSession, {
|
|
6
|
-
get(target, key) {
|
|
7
|
-
if (target[key]) return target[key];
|
|
8
|
-
if (key === "then") return target[key];
|
|
9
|
-
if (typeof key === "symbol") return target[key];
|
|
10
|
-
throw ServerError.NO_STORE_READ({ key });
|
|
11
|
-
},
|
|
12
|
-
set(target, key, value) {
|
|
13
|
-
if (target[key] || key === "then" || typeof key === "symbol") {
|
|
14
|
-
target[key] = value;
|
|
15
|
-
} else {
|
|
16
|
-
throw ServerError.NO_STORE_WRITE({ key });
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import "../test/toSucceed.js";
|
|
2
|
-
|
|
3
|
-
import kv from "polystore";
|
|
4
|
-
|
|
5
|
-
import server from "../index.js";
|
|
6
|
-
|
|
7
|
-
describe("user creation flow", () => {
|
|
8
|
-
// These are obviously mock data
|
|
9
|
-
const EMAIL = "abc@test.com";
|
|
10
|
-
const PASS = "11111111";
|
|
11
|
-
const CREDENTIALS = { email: EMAIL, password: PASS };
|
|
12
|
-
|
|
13
|
-
const store = kv(new Map());
|
|
14
|
-
const api = server({ store, auth: "cookie:email" })
|
|
15
|
-
.get("/me", (ctx) => ctx.user || "No data")
|
|
16
|
-
.test();
|
|
17
|
-
|
|
18
|
-
// Bun's bug: https://github.com/oven-sh/bun/issues/6348
|
|
19
|
-
it("can create a new user", async () => {
|
|
20
|
-
const register = await api.post("/auth/register/email", CREDENTIALS);
|
|
21
|
-
expect(register).toSucceed();
|
|
22
|
-
expect(await store.keys()).toEqual([
|
|
23
|
-
"user:abc@test.com",
|
|
24
|
-
`auth:${register.headers["set-cookie"].split(";")[0].split("=")[1]}`,
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
const me = await api.get("/me");
|
|
28
|
-
expect(me).toSucceed();
|
|
29
|
-
expect(me.body.email).toEqual(EMAIL);
|
|
30
|
-
|
|
31
|
-
const logout = await api.post("/auth/logout");
|
|
32
|
-
expect(logout).toSucceed();
|
|
33
|
-
expect(await store.keys()).toEqual(["user:abc@test.com"]);
|
|
34
|
-
|
|
35
|
-
const login = await api.post("/auth/login/email", CREDENTIALS);
|
|
36
|
-
expect(login).toSucceed();
|
|
37
|
-
expect(await store.keys()).toEqual([
|
|
38
|
-
"user:abc@test.com",
|
|
39
|
-
`auth:${login.headers["set-cookie"].split(";")[0].split("=")[1]}`,
|
|
40
|
-
]);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import "../test/toSucceed.js";
|
|
2
|
-
|
|
3
|
-
import kv from "polystore";
|
|
4
|
-
|
|
5
|
-
import server from "../index.js";
|
|
6
|
-
|
|
7
|
-
describe("user creation flow", () => {
|
|
8
|
-
// These are obviously mock data
|
|
9
|
-
const EMAIL = "abc@test.com";
|
|
10
|
-
const PASS = "11111111";
|
|
11
|
-
const CREDENTIALS = { email: EMAIL, password: PASS };
|
|
12
|
-
|
|
13
|
-
const store = kv(new Map());
|
|
14
|
-
const sessions = () => store.prefix("auth:").keys();
|
|
15
|
-
const users = () => store.prefix("user:").keys();
|
|
16
|
-
const api = server({ store, auth: "token:email" })
|
|
17
|
-
.get("/me", (ctx) => ctx.user || "No data")
|
|
18
|
-
.test();
|
|
19
|
-
|
|
20
|
-
// Bun's bug: https://github.com/oven-sh/bun/issues/6348
|
|
21
|
-
it("tests a long user flow with tokens", async () => {
|
|
22
|
-
// The latest updated token
|
|
23
|
-
let token;
|
|
24
|
-
|
|
25
|
-
// REGISTER A NEW USER
|
|
26
|
-
token = await (async () => {
|
|
27
|
-
const register = await api.post("/auth/register/email", CREDENTIALS);
|
|
28
|
-
expect(register).toSucceed();
|
|
29
|
-
|
|
30
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
31
|
-
expect(await sessions()).toEqual([register.body.token]);
|
|
32
|
-
return register.body.token;
|
|
33
|
-
})();
|
|
34
|
-
|
|
35
|
-
// CAN GET MY OWN INFO
|
|
36
|
-
await (async () => {
|
|
37
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
38
|
-
const me = await api.get("/me", { headers });
|
|
39
|
-
expect(me).toSucceed();
|
|
40
|
-
expect(me.body.email).toEqual(EMAIL);
|
|
41
|
-
|
|
42
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
43
|
-
expect(await sessions()).toEqual([token]);
|
|
44
|
-
})();
|
|
45
|
-
|
|
46
|
-
// LOGOUT TEST
|
|
47
|
-
await (async () => {
|
|
48
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
49
|
-
const logout = await api.post("/auth/logout", {}, { headers });
|
|
50
|
-
expect(logout).toSucceed();
|
|
51
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
52
|
-
expect(await sessions()).toEqual([]);
|
|
53
|
-
})();
|
|
54
|
-
|
|
55
|
-
// LOGIN FOR THE FIRST TIME
|
|
56
|
-
token = await (async () => {
|
|
57
|
-
const login = await api.post("/auth/login/email", CREDENTIALS);
|
|
58
|
-
expect(login).toSucceed();
|
|
59
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
60
|
-
expect(await sessions()).toEqual([login.body.token]);
|
|
61
|
-
return login.body.token;
|
|
62
|
-
})();
|
|
63
|
-
|
|
64
|
-
// CAN GET MY OWN INFO
|
|
65
|
-
await (async () => {
|
|
66
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
67
|
-
const me = await api.get("/me", { headers });
|
|
68
|
-
expect(me).toSucceed();
|
|
69
|
-
expect(me.body.email).toEqual(EMAIL);
|
|
70
|
-
})();
|
|
71
|
-
|
|
72
|
-
// UPDATE PASSWORD
|
|
73
|
-
await (async () => {
|
|
74
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
75
|
-
const body = { previous: PASS, updated: "22222222" };
|
|
76
|
-
const update = await api.put("/auth/password/email", body, { headers });
|
|
77
|
-
expect(update).toSucceed();
|
|
78
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
79
|
-
expect(await sessions()).toEqual([token]);
|
|
80
|
-
})();
|
|
81
|
-
|
|
82
|
-
// LOGOUT AGAIN
|
|
83
|
-
await (async () => {
|
|
84
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
85
|
-
const logout = await api.post("/auth/logout", {}, { headers });
|
|
86
|
-
expect(logout).toSucceed();
|
|
87
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
88
|
-
expect(await sessions()).toEqual([]);
|
|
89
|
-
})();
|
|
90
|
-
|
|
91
|
-
// LOGIN WITH OLD PASSWORD
|
|
92
|
-
await (async () => {
|
|
93
|
-
const login = await api.post("/auth/login/email", CREDENTIALS);
|
|
94
|
-
expect(login).not.toSucceed();
|
|
95
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
96
|
-
})();
|
|
97
|
-
|
|
98
|
-
// LOGIN WITH NEW PASSWORD
|
|
99
|
-
token = await (async () => {
|
|
100
|
-
const login = await api.post("/auth/login/email", {
|
|
101
|
-
...CREDENTIALS,
|
|
102
|
-
password: "22222222",
|
|
103
|
-
});
|
|
104
|
-
expect(login).toSucceed();
|
|
105
|
-
expect(await users()).toEqual(["abc@test.com"]);
|
|
106
|
-
expect(await sessions()).toEqual([login.body.token]);
|
|
107
|
-
return login.body.token;
|
|
108
|
-
})();
|
|
109
|
-
});
|
|
110
|
-
});
|
package/src/auth/auth.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { ServerError } from "../index.js";
|
|
2
|
-
|
|
3
|
-
const validateToken = (authorization) => {
|
|
4
|
-
const [type, id] = authorization.trim().split(" ");
|
|
5
|
-
if (type.toLowerCase() !== "bearer") {
|
|
6
|
-
throw ServerError.AUTH_INVALID_TYPE({ type });
|
|
7
|
-
}
|
|
8
|
-
if (id.length !== 16) {
|
|
9
|
-
throw ServerError.AUTH_INVALID_TOKEN();
|
|
10
|
-
}
|
|
11
|
-
return id;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const validateCookie = (authorization) => {
|
|
15
|
-
if (authorization.length !== 16) {
|
|
16
|
-
throw ServerError.AUTH_INVALID_COOKIE();
|
|
17
|
-
}
|
|
18
|
-
return authorization;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const findSessionId = (ctx) => {
|
|
22
|
-
const type = ctx.options.auth.type;
|
|
23
|
-
|
|
24
|
-
if (type === "token") {
|
|
25
|
-
// If the user is not authenticated, there's no auth to retrieve
|
|
26
|
-
if (!ctx.headers.authorization) return;
|
|
27
|
-
|
|
28
|
-
// Check the authentication header
|
|
29
|
-
return validateToken(ctx.headers.authorization);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (type === "cookie") {
|
|
33
|
-
// If the user is not authenticated, there's no auth to retrieve
|
|
34
|
-
if (!ctx.cookies.authentication) return;
|
|
35
|
-
|
|
36
|
-
return validateCookie(ctx.cookies.authentication);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
throw new Error(`Invalid auth type "${type}"`);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default async function auth(ctx) {
|
|
43
|
-
if (!ctx.options.auth) return; // NO AUTH AT ALL; nothing to do here
|
|
44
|
-
const options = ctx.options.auth;
|
|
45
|
-
|
|
46
|
-
const sessionId = findSessionId(ctx);
|
|
47
|
-
if (!sessionId) return; // NO SESSION FOUND; no auth
|
|
48
|
-
|
|
49
|
-
const auth = await options.session.get(sessionId);
|
|
50
|
-
// Mmh, which one to do...
|
|
51
|
-
// if (!auth) throw ServerError.AUTH_NO_SESSION();
|
|
52
|
-
if (!auth) return; // SESSION ALREADY INVALID; no auth
|
|
53
|
-
|
|
54
|
-
if (!auth.provider) throw ServerError.AUTH_NO_PROVIDER();
|
|
55
|
-
if (!options.provider.includes(auth.provider)) {
|
|
56
|
-
throw ServerError.AUTH_INVALID_PROVIDER({
|
|
57
|
-
provider: auth.provider,
|
|
58
|
-
valid: options.provider,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return auth;
|
|
62
|
-
}
|
package/src/auth/index.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import auth from "./auth.js";
|
|
2
|
-
import logout from "./logout.js";
|
|
3
|
-
import providers from "./providers/index.js";
|
|
4
|
-
import session from "./session.js";
|
|
5
|
-
import user from "./user.js";
|
|
6
|
-
|
|
7
|
-
const parseOptions = (auth, all) => {
|
|
8
|
-
if (!auth) return null;
|
|
9
|
-
|
|
10
|
-
if (typeof auth === "string") {
|
|
11
|
-
const [type, provider] = auth.split(":");
|
|
12
|
-
auth = { type, provider };
|
|
13
|
-
}
|
|
14
|
-
// if (typeof auth.type === "string") {
|
|
15
|
-
// auth.type = auth.type.split("|").filter(Boolean);
|
|
16
|
-
// }
|
|
17
|
-
if (typeof auth.provider === "string") {
|
|
18
|
-
auth.provider = auth.provider.split("|").filter(Boolean);
|
|
19
|
-
}
|
|
20
|
-
if (!auth.type) {
|
|
21
|
-
throw new Error("Auth options needs a type");
|
|
22
|
-
}
|
|
23
|
-
// if (!auth.type.length) {
|
|
24
|
-
// throw new Error("Auth options needs a type");
|
|
25
|
-
// }
|
|
26
|
-
if (!auth.provider || !auth.provider.length) {
|
|
27
|
-
throw new Error("Auth options needs a provider");
|
|
28
|
-
}
|
|
29
|
-
const providerNotFound = auth.provider.find((p) => !providers[p]);
|
|
30
|
-
if (providerNotFound) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Provider "${providerNotFound}" not found, available ones are "${Object.keys(providers).join('", "')}"`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!auth.session && all.store) {
|
|
37
|
-
auth.session = all.store.prefix("auth:");
|
|
38
|
-
}
|
|
39
|
-
if (!auth.store && all.store) {
|
|
40
|
-
auth.store = all.store.prefix("user:");
|
|
41
|
-
}
|
|
42
|
-
if (!auth.cleanUser) {
|
|
43
|
-
auth.cleanUser = (fullUser) => {
|
|
44
|
-
const { password, token, ...user } = fullUser;
|
|
45
|
-
return user;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (!auth.redirect) {
|
|
49
|
-
auth.redirect = "/user";
|
|
50
|
-
}
|
|
51
|
-
return auth;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const load = async (ctx) => {
|
|
55
|
-
ctx.session = await session(ctx);
|
|
56
|
-
ctx.auth = await auth(ctx);
|
|
57
|
-
ctx.user = await user(ctx);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const middle = async (ctx) => {
|
|
61
|
-
if (ctx.options.auth) {
|
|
62
|
-
if (ctx.options.auth.provider.includes("github")) {
|
|
63
|
-
if (!env.GITHUB_ID) throw new Error("GITHUB_ID not defined");
|
|
64
|
-
if (!env.GITHUB_SECRET) throw new Error("GITHUB_SECRET not defined");
|
|
65
|
-
ctx.app.get(
|
|
66
|
-
"/auth/logout",
|
|
67
|
-
{ tags: "Auth", title: "Github logout" },
|
|
68
|
-
logout,
|
|
69
|
-
);
|
|
70
|
-
ctx.app.get(
|
|
71
|
-
"/auth/login/github",
|
|
72
|
-
{ tags: "Auth" },
|
|
73
|
-
providers.github.login,
|
|
74
|
-
);
|
|
75
|
-
ctx.app.get(
|
|
76
|
-
"/auth/callback/github",
|
|
77
|
-
{ tags: "Auth", title: "Github callback" },
|
|
78
|
-
providers.github.callback,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (ctx.options.auth.provider.includes("email")) {
|
|
83
|
-
ctx.app.post("/auth/logout", { tags: "Auth" }, logout);
|
|
84
|
-
ctx.app.post(
|
|
85
|
-
"/auth/register/email",
|
|
86
|
-
{ tags: "Auth" },
|
|
87
|
-
providers.email.register,
|
|
88
|
-
);
|
|
89
|
-
ctx.app.post(
|
|
90
|
-
"/auth/login/email",
|
|
91
|
-
{ tags: "Auth" },
|
|
92
|
-
providers.email.login,
|
|
93
|
-
);
|
|
94
|
-
ctx.app.put(
|
|
95
|
-
"/auth/password/email",
|
|
96
|
-
{ tags: "Auth" },
|
|
97
|
-
providers.email.password,
|
|
98
|
-
);
|
|
99
|
-
ctx.app.put("/auth/reset/email", { tags: "Auth" }, providers.email.reset);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export default { load, parseOptions, middle };
|
package/src/auth/index.test.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import "../test/toSucceed.js";
|
|
2
|
-
|
|
3
|
-
import kv from "polystore";
|
|
4
|
-
|
|
5
|
-
import server from "../index.js";
|
|
6
|
-
|
|
7
|
-
const ID = "REqA2l022l8Q0tuI";
|
|
8
|
-
|
|
9
|
-
describe("auth", () => {
|
|
10
|
-
it("requires a provider", async () => {
|
|
11
|
-
expect(() => server({ auth: "token" })).toThrow(
|
|
12
|
-
"Auth options needs a provider",
|
|
13
|
-
);
|
|
14
|
-
expect(() => server({ auth: "token:" })).toThrow(
|
|
15
|
-
"Auth options needs a provider",
|
|
16
|
-
);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("requires a valid provider", async () => {
|
|
20
|
-
expect(() => server({ auth: "token:nonexisting" })).toThrow(
|
|
21
|
-
/Provider \"nonexisting\" not found, available ones are/,
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("provider must belong", async () => {
|
|
26
|
-
const store = kv(new Map());
|
|
27
|
-
const api = server({ store, auth: "token:email" })
|
|
28
|
-
.get("/", (ctx) => ctx.auth)
|
|
29
|
-
.test();
|
|
30
|
-
|
|
31
|
-
store.set(`auth:${ID}`, {
|
|
32
|
-
id: ID,
|
|
33
|
-
type: "token",
|
|
34
|
-
provider: "wrong",
|
|
35
|
-
user: "QypOn5SQApyOPdUp",
|
|
36
|
-
email: "abc@test.com",
|
|
37
|
-
time: "2024-07-01T03:21:40Z",
|
|
38
|
-
});
|
|
39
|
-
const authorization = "Bearer REqA2l022l8Q0tuI";
|
|
40
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
41
|
-
expect(req).not.toSucceed(
|
|
42
|
-
"Invalid provider 'wrong', valid ones are: 'email'",
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("token", () => {
|
|
48
|
-
const store = kv(new Map());
|
|
49
|
-
const api = server({ store, auth: "token:email" })
|
|
50
|
-
.get("/", (ctx) => ctx.auth)
|
|
51
|
-
.test();
|
|
52
|
-
|
|
53
|
-
afterEach(async () => {
|
|
54
|
-
await store.del(`auth:${ID}`);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should be Bearer", async () => {
|
|
58
|
-
const authorization = "Basic REqA2l022l8Q0tuI";
|
|
59
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
60
|
-
expect(req).not.toSucceed("Invalid Authorization type, 'Basic'");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should have the proper token", async () => {
|
|
64
|
-
const authorization = "Bearer hola";
|
|
65
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
66
|
-
expect(req).not.toSucceed("Invalid Authorization token");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("cannot get the session", async () => {
|
|
70
|
-
const authorization = "Bearer REqA2l022l8Q0tuI";
|
|
71
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
72
|
-
expect(req.status).toBe(404);
|
|
73
|
-
// expect(req).not.toSucceed("Invalid session");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("cannot get the user", async () => {
|
|
77
|
-
store.set(`auth:${ID}`, {
|
|
78
|
-
id: ID,
|
|
79
|
-
type: "token",
|
|
80
|
-
provider: "email",
|
|
81
|
-
user: "QypOn5SQApyOPdUp",
|
|
82
|
-
email: "abc@test.com",
|
|
83
|
-
time: "2024-07-01T03:21:40Z",
|
|
84
|
-
});
|
|
85
|
-
const authorization = "Bearer REqA2l022l8Q0tuI";
|
|
86
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
87
|
-
expect(req).not.toSucceed("Credentials do not correspond to a user");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("cannot get the user", async () => {
|
|
91
|
-
store.set("auth:REqA2l022l8Q0tuI", {
|
|
92
|
-
id: "REqA2l022l8Q0tuI",
|
|
93
|
-
type: "token",
|
|
94
|
-
provider: "email",
|
|
95
|
-
user: "QypOn5SQApyOPdUp",
|
|
96
|
-
email: "abc@test.com",
|
|
97
|
-
time: "2024-07-01T03:21:40Z",
|
|
98
|
-
});
|
|
99
|
-
store.set("user:QypOn5SQApyOPdUp", {
|
|
100
|
-
id: "QypOn5SQApyOPdUp",
|
|
101
|
-
email: "abc@test.com",
|
|
102
|
-
});
|
|
103
|
-
const authorization = "Bearer REqA2l022l8Q0tuI";
|
|
104
|
-
const req = await api.get("/", { headers: { authorization } });
|
|
105
|
-
expect(req).toSucceed();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("cookie", () => {
|
|
110
|
-
const store = kv(new Map());
|
|
111
|
-
const api = server({ store, auth: "cookie:email" })
|
|
112
|
-
.get("/", (ctx) => ctx.auth)
|
|
113
|
-
.test();
|
|
114
|
-
|
|
115
|
-
it("should have the proper token in email", async () => {
|
|
116
|
-
const cookie = "authentication=hello";
|
|
117
|
-
const req = await api.get("/", { headers: { cookie } });
|
|
118
|
-
expect(req).not.toSucceed("Invalid Authorization cookie");
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("can get the proper session", async () => {
|
|
122
|
-
const cookie = "authentication=REqA2l022l8Q0tuI";
|
|
123
|
-
const req = await api.get("/", { headers: { cookie } });
|
|
124
|
-
expect(req.status).toBe(404);
|
|
125
|
-
// expect(req).not.toSucceed("Invalid session");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("can get the proper session", async () => {
|
|
129
|
-
const cookie = "authentication=REqA2l022l8Q0tuI";
|
|
130
|
-
const req = await api.get("/", { headers: { cookie } });
|
|
131
|
-
expect(req).not.toSucceed();
|
|
132
|
-
});
|
|
133
|
-
});
|