@server/next 0.20.10 → 0.20.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.20.10",
3
+ "version": "0.20.12",
4
4
  "description": "An experimental reimplementation of server.js focused on the DX",
5
5
  "homepage": "https://node-server.com/",
6
6
  "repository": "https://github.com/franciscop/server-next.git",
@@ -11,32 +11,99 @@ describe("user creation flow", () => {
11
11
  const CREDENTIALS = { email: EMAIL, password: PASS };
12
12
 
13
13
  const store = kv(new Map());
14
+ const sessions = () => store.prefix("auth:").keys();
15
+ const users = () => store.prefix("user:").keys();
14
16
  const api = server({ store, auth: "token:email" })
15
17
  .get("/me", (ctx) => ctx.user || "No data")
16
18
  .test();
17
19
 
18
- it("can create a new user", async () => {
19
- const register = await api.post("/auth/register/email", CREDENTIALS);
20
- expect(register).toSucceed();
21
- expect(await store.keys()).toEqual([
22
- "user:abc@test.com",
23
- "auth:" + register.data.token,
24
- ]);
25
-
26
- const headers = { authorization: "Bearer " + register.data.token };
27
- const me = await api.get("/me", { headers });
28
- expect(me).toSucceed();
29
- expect(me.data.email).toEqual(EMAIL);
30
-
31
- const logout = await api.post("/auth/logout", {}, { headers });
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.data.token,
40
- ]);
20
+ it("tests a long user flow with tokens", async () => {
21
+ // The latest updated token
22
+ let token;
23
+
24
+ // REGISTER A NEW USER
25
+ token = await (async () => {
26
+ const register = await api.post("/auth/register/email", CREDENTIALS);
27
+ expect(register).toSucceed();
28
+
29
+ expect(await users()).toEqual(["abc@test.com"]);
30
+ expect(await sessions()).toEqual([register.data.token]);
31
+ return register.data.token;
32
+ })();
33
+
34
+ // CAN GET MY OWN INFO
35
+ await (async () => {
36
+ const headers = { authorization: "Bearer " + token };
37
+ const me = await api.get("/me", { headers });
38
+ expect(me).toSucceed();
39
+ expect(me.data.email).toEqual(EMAIL);
40
+
41
+ expect(await users()).toEqual(["abc@test.com"]);
42
+ expect(await sessions()).toEqual([token]);
43
+ })();
44
+
45
+ // LOGOUT TEST
46
+ await (async () => {
47
+ const headers = { authorization: "Bearer " + token };
48
+ const logout = await api.post("/auth/logout", {}, { headers });
49
+ expect(logout).toSucceed();
50
+ expect(await users()).toEqual(["abc@test.com"]);
51
+ expect(await sessions()).toEqual([]);
52
+ })();
53
+
54
+ // LOGIN FOR THE FIRST TIME
55
+ token = await (async () => {
56
+ const login = await api.post("/auth/login/email", CREDENTIALS);
57
+ expect(login).toSucceed();
58
+ expect(await users()).toEqual(["abc@test.com"]);
59
+ expect(await sessions()).toEqual([login.data.token]);
60
+ return login.data.token;
61
+ })();
62
+
63
+ // CAN GET MY OWN INFO
64
+ await (async () => {
65
+ const headers = { authorization: "Bearer " + token };
66
+ const me = await api.get("/me", { headers });
67
+ expect(me).toSucceed();
68
+ expect(me.data.email).toEqual(EMAIL);
69
+ })();
70
+
71
+ // UPDATE PASSWORD
72
+ await (async () => {
73
+ const headers = { authorization: "Bearer " + token };
74
+ const body = { previous: PASS, updated: "22222222" };
75
+ const update = await api.put("/auth/password/email", body, { headers });
76
+ expect(update).toSucceed();
77
+ expect(await users()).toEqual(["abc@test.com"]);
78
+ expect(await sessions()).toEqual([token]);
79
+ })();
80
+
81
+ // LOGOUT AGAIN
82
+ await (async () => {
83
+ const headers = { authorization: "Bearer " + token };
84
+ const logout = await api.post("/auth/logout", {}, { headers });
85
+ expect(logout).toSucceed();
86
+ expect(await users()).toEqual(["abc@test.com"]);
87
+ expect(await sessions()).toEqual([]);
88
+ })();
89
+
90
+ // LOGIN WITH OLD PASSWORD
91
+ await (async () => {
92
+ const login = await api.post("/auth/login/email", CREDENTIALS);
93
+ expect(login).not.toSucceed();
94
+ expect(await users()).toEqual(["abc@test.com"]);
95
+ })();
96
+
97
+ // LOGIN WITH NEW PASSWORD
98
+ token = await (async () => {
99
+ const login = await api.post("/auth/login/email", {
100
+ ...CREDENTIALS,
101
+ password: "22222222",
102
+ });
103
+ expect(login).toSucceed();
104
+ expect(await users()).toEqual(["abc@test.com"]);
105
+ expect(await sessions()).toEqual([login.data.token]);
106
+ return login.data.token;
107
+ })();
41
108
  });
42
109
  });
package/src/auth/auth.js CHANGED
@@ -48,5 +48,10 @@ export default async function findAuth(ctx) {
48
48
 
49
49
  const auth = await options.session.get(sessionId);
50
50
  if (!auth) throw ServerError.AUTH_NO_SESSION();
51
+ if (!auth.provider) throw ServerError.AUTH_NO_PROVIDER();
52
+ if (!options.provider.includes(auth.provider)) {
53
+ const valid = JSON.stringify(options.provider);
54
+ throw ServerError.AUTH_INVALID_PROVIDER({ provider: auth.provider, valid });
55
+ }
51
56
  return auth;
52
57
  }
@@ -0,0 +1,5 @@
1
+ export default async function findUser(auth, store) {
2
+ if (auth.provider === "email") {
3
+ return await store.get(auth.email);
4
+ }
5
+ }
package/src/auth/index.js CHANGED
@@ -17,6 +17,8 @@ const middle = async (ctx) => {
17
17
  if (ctx.options.auth.provider.includes("email")) {
18
18
  ctx.app.post("/auth/register/email", providers.email.register);
19
19
  ctx.app.post("/auth/login/email", providers.email.login);
20
+ ctx.app.put("/auth/password/email", providers.email.password);
21
+ ctx.app.put("/auth/reset/email", providers.email.password);
20
22
  }
21
23
  }
22
24
  };
@@ -2,6 +2,8 @@ import argon2 from "argon2";
2
2
 
3
3
  import { createId } from "../../helpers/index.js";
4
4
  import { ServerError, status } from "../../index.js";
5
+ import findUser from "../findUser.js";
6
+ import updateUser from "../updateUser.js";
5
7
 
6
8
  const createSession = async (user, ctx) => {
7
9
  const { type, session, cleanUser } = ctx.options.auth;
@@ -70,4 +72,40 @@ async function register(ctx) {
70
72
  return createSession(user, ctx);
71
73
  }
72
74
 
73
- export default { login, register };
75
+ async function reset(ctx) {
76
+ // const reset = ctx.options.store.prefix("reset:");
77
+ // // Already resetting
78
+ // if (ctx.body.token) {
79
+ // const { token, password } = ctx.body;
80
+ // const secret = sha256(token);
81
+ // const auth = await reset.get(secret);
82
+ // const user = await store.get(auth.email);
83
+ // } else {
84
+ // const email = ctx.body.email;
85
+ // const user = await store.get(email);
86
+ // const token = createId();
87
+ // const secret = sha256(token);
88
+ // await reset.set(secret, { email }, { expires: "2h" });
89
+ // //
90
+ // ctx.email.send(
91
+ // user.email,
92
+ // `Reset email: <a href="${domain}/auth/reset/email?token=${token}">`
93
+ // );
94
+ // }
95
+ }
96
+
97
+ async function password(ctx) {
98
+ const { previous, updated } = ctx.body;
99
+
100
+ const fullUser = await findUser(ctx.auth, ctx.options.auth.store);
101
+
102
+ const isValid = await argon2.verify(fullUser.password, previous);
103
+ if (!isValid) throw ServerError.LOGIN_WRONG_PASSWORD();
104
+
105
+ fullUser.password = await argon2.hash(updated);
106
+ await updateUser(fullUser, ctx.auth, ctx.options.auth.store);
107
+
108
+ return 200;
109
+ }
110
+
111
+ export default { login, register, reset, password };
@@ -0,0 +1,6 @@
1
+ // At this point we can assume that "auth" has already been validated
2
+ export default async function updateUser(user, auth, store) {
3
+ if (auth.provider === "email") {
4
+ return await store.set(auth.email, user);
5
+ }
6
+ }
package/src/auth/user.js CHANGED
@@ -1,21 +1,10 @@
1
- import ServerError from "../ServerError";
2
-
3
- const findUser = async (auth, options) => {
4
- if (!auth.provider) throw ServerError.AUTH_NO_PROVIDER();
5
- if (!options.provider.includes(auth.provider)) {
6
- const valid = JSON.stringify(options.provider);
7
- throw ServerError.AUTH_INVALID_PROVIDER({ provider: auth.provider, valid });
8
- }
9
-
10
- if (auth.provider === "email") {
11
- return await options.store.get(auth.email);
12
- }
13
- };
1
+ import ServerError from "../ServerError.js";
2
+ import findUser from "./findUser.js";
14
3
 
15
4
  export default async function user(ctx) {
16
5
  if (!ctx.auth) return;
17
6
 
18
- const user = await findUser(ctx.auth, ctx.options.auth);
7
+ const user = await findUser(ctx.auth, ctx.options.auth.store);
19
8
  if (!user) throw ServerError.AUTH_NO_USER();
20
9
  return ctx.options.auth.cleanUser(user);
21
10
  }
@@ -1,5 +1,5 @@
1
1
  import auth from "../auth/index.js";
2
- import { define } from "../helpers/index.js";
2
+ import { define, parseHeaders } from "../helpers/index.js";
3
3
  import parseBody from "./parseBody.js";
4
4
  import parseCookies from "./parseCookies.js";
5
5
 
package/src/index.js CHANGED
@@ -31,7 +31,7 @@ const createNodeServer = async (app, options) => {
31
31
  extendWithDefaults(ctx);
32
32
  const out = await handleRequest(app.handlers, ctx);
33
33
 
34
- response.writeHead(out.status || 200, out.headers);
34
+ response.writeHead(out.status || 200, parseHeaders(out.headers));
35
35
  if (out.body instanceof ReadableStream) {
36
36
  await iterate(out.body, (chunk) => response.write(chunk));
37
37
  } else {