@stanlemon/server-with-auth 0.2.12 → 0.3.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.
@@ -4,11 +4,11 @@ import { v4 as uuidv4 } from "uuid";
4
4
  import bcrypt from "bcryptjs";
5
5
  import UserDao from "./user-dao.js";
6
6
 
7
- export function createInMemoryDb() {
7
+ export function createInMemoryLowDb() {
8
8
  return new LowSync(new MemorySync(), {});
9
9
  }
10
10
 
11
- export function createJsonFileDb(filename = "./db.json") {
11
+ export function createJsonFileLowDb(filename = "./db.json") {
12
12
  return new LowSync(new JSONFileSync(filename), {});
13
13
  }
14
14
 
@@ -17,16 +17,16 @@ export function createJsonFileDb(filename = "./db.json") {
17
17
  * Test environment (NODE_ENV=test) will use {MemorySync}.
18
18
  * @returns {LowSync} Database
19
19
  */
20
- export function createDb() {
20
+ export function createLowDb() {
21
21
  return process.env.NODE_ENV === "test"
22
- ? createInMemoryDb()
23
- : createJsonFileDb();
22
+ ? createInMemoryLowDb()
23
+ : createJsonFileLowDb();
24
24
  }
25
25
 
26
26
  export default class LowDBUserDao extends UserDao {
27
27
  #db;
28
28
 
29
- constructor(db = createDb()) {
29
+ constructor(db = createLowDb()) {
30
30
  super();
31
31
 
32
32
  if (!(db instanceof LowSync)) {
@@ -42,72 +42,86 @@ export default class LowDBUserDao extends UserDao {
42
42
 
43
43
  /** @inheritdoc */
44
44
  getUserById(userId) {
45
- return this.#db.data.users
46
- .filter((user) => {
47
- // This one can get gross with numerical ids
48
- // eslint-disable-next-line eqeqeq
49
- return user.id == userId;
50
- })
51
- .shift();
45
+ return new Promise((resolve) => {
46
+ resolve(
47
+ this.#db.data.users
48
+ .filter((user) => {
49
+ // This one can get gross with numerical ids
50
+ // eslint-disable-next-line eqeqeq
51
+ return user.id == userId;
52
+ })
53
+ .shift()
54
+ );
55
+ });
52
56
  }
53
57
 
54
58
  /** @inheritdoc */
55
59
  getUserByUsername(username) {
56
- return this.#db.data.users
57
- .filter((user) => {
58
- return user.username === username;
59
- })
60
- .shift();
60
+ return new Promise((resolve) => {
61
+ resolve(
62
+ this.#db.data.users
63
+ .filter((user) => {
64
+ return user.username === username;
65
+ })
66
+ .shift()
67
+ );
68
+ });
61
69
  }
62
70
 
63
71
  /** @inheritdoc */
64
72
  getUserByUsernameAndPassword(username, password) {
65
73
  // Treat this like a failed login.
66
74
  if (!username || !password) {
67
- return false;
68
- }
69
-
70
- const user = this.getUserByUsername(username);
71
-
72
- if (!user || !bcrypt.compareSync(password, user.password)) {
73
- return false;
75
+ return new Promise((resolve) => {
76
+ resolve(false);
77
+ });
74
78
  }
75
79
 
76
- return user;
80
+ return this.getUserByUsername(username).then((user) => {
81
+ if (!user || !bcrypt.compareSync(password, user.password)) {
82
+ return false;
83
+ }
84
+ return user;
85
+ });
77
86
  }
78
87
 
79
88
  /** @inheritdoc */
80
89
  getUserByVerificationToken(token) {
81
- return this.#db.data.users
82
- .filter((user) => user.verification_token === token)
83
- .shift();
90
+ return new Promise((resolve) => {
91
+ resolve(
92
+ this.#db.data.users
93
+ .filter((user) => user.verification_token === token)
94
+ .shift()
95
+ );
96
+ });
84
97
  }
85
98
 
86
99
  /** @inheritdoc */
87
100
  createUser(user) {
88
- const existing = this.getUserByUsername(user.username);
101
+ return this.getUserByUsername(user.username).then((existing) => {
102
+ if (existing) {
103
+ throw new Error("This username is already taken.");
104
+ }
89
105
 
90
- if (existing) {
91
- throw new Error("This username is already taken.");
92
- }
106
+ const now = new Date();
93
107
 
94
- const now = new Date();
95
- const data = {
96
- ...user,
97
- password: bcrypt.hashSync(user.password, 10),
98
- id: uuidv4(),
99
- verification_token: uuidv4(),
100
- created_at: now,
101
- last_updated: now,
102
- };
103
- this.#db.data.users.push(data);
104
- this.#db.write();
105
- return data;
108
+ const data = {
109
+ ...user,
110
+ password: bcrypt.hashSync(user.password, 10),
111
+ id: uuidv4(),
112
+ verification_token: uuidv4(),
113
+ created_at: now,
114
+ last_updated: now,
115
+ };
116
+ this.#db.data.users.push(data);
117
+ this.#db.write();
118
+
119
+ return data;
120
+ });
106
121
  }
107
122
 
108
123
  /** @inheritdoc */
109
124
  updateUser(userId, user) {
110
- const now = new Date();
111
125
  this.#db.data.users = this.#db.data.users.map((u) => {
112
126
  if (u.id === userId) {
113
127
  // If the password has been set, encrypt it
@@ -119,19 +133,35 @@ export default class LowDBUserDao extends UserDao {
119
133
  ...u,
120
134
  ...user,
121
135
  id: userId,
122
- last_updated: now,
136
+ last_updated: new Date(),
137
+ created_at: u.created_at, // Always preserve created_at
123
138
  };
124
139
  }
125
140
  return u;
126
141
  });
127
142
  this.#db.write();
128
- return this.getUserById(userId);
143
+
144
+ return new Promise((resolve) => {
145
+ resolve(this.getUserById(userId));
146
+ });
129
147
  }
130
148
 
131
149
  /** @inheritdoc */
132
150
  deleteUser(userId) {
133
151
  this.#db.data.users = this.#db.data.users.filter((u) => u.id !== userId);
134
152
  this.#db.write();
135
- return true;
153
+ return new Promise((resolve) => {
154
+ resolve(true);
155
+ });
136
156
  }
157
+
158
+ /** @inheritdoc */
159
+ getAllUsers() {
160
+ return new Promise((resolve) => {
161
+ resolve(this.#db.data.users || []);
162
+ });
163
+ }
164
+
165
+ /** @inheritdoc */
166
+ close() {}
137
167
  }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @typedef User
3
+ * @property {string|int} id Identifier
4
+ * @property {string} username Username
5
+ * @property {string} password Password
6
+ * @property {string} verification_token Verification token
7
+ * @property {date} verify_at Date verified
8
+ * @property {date} last_logged_in Date last logged in
9
+ * @property {date} created_at Date created
10
+ * @property {date} last_updated Date last updated
11
+ */
1
12
  export default class UserDao {
2
13
  constructor() {}
3
14
 
@@ -9,9 +20,9 @@ export default class UserDao {
9
20
 
10
21
  /**
11
22
  * Get user by id.
12
- * @param {*} userId Identifier to get user by
23
+ * @param {string|int} userId Identifier to get user by
24
+ * @returns {Promise<User|boolean>} User object or false
13
25
  */
14
- // eslint-disable-next-line no-unused-vars
15
26
  getUserById(userId) {
16
27
  this.#error();
17
28
  }
@@ -19,8 +30,8 @@ export default class UserDao {
19
30
  /**
20
31
  * Get user by username.
21
32
  * @param {string} username Username
33
+ * @returns {Promise<User|boolean>} User object or false
22
34
  */
23
- // eslint-disable-next-line no-unused-vars
24
35
  getUserByUsername(username) {
25
36
  this.#error();
26
37
  }
@@ -29,8 +40,8 @@ export default class UserDao {
29
40
  * Get user by username and password.
30
41
  * @param {string} username Username
31
42
  * @param {string} password Password
43
+ * @returns {Promise<User|boolean>} User object or false
32
44
  */
33
- // eslint-disable-next-line no-unused-vars
34
45
  getUserByUsernameAndPassword(username, password) {
35
46
  this.#error();
36
47
  }
@@ -38,36 +49,52 @@ export default class UserDao {
38
49
  /**
39
50
  * Get user by verification token.
40
51
  * @param {string} token User verification token
52
+ * @returns {Promise<User|boolean>} User object or false
41
53
  */
42
- // eslint-disable-next-line no-unused-vars
43
54
  getUserByVerificationToken(token) {
44
55
  this.#error();
45
56
  }
46
57
 
47
58
  /**
48
59
  * Create a new user.
49
- * @param {object} user User
60
+ * @param {User} user User
61
+ * @returns {Promise<User|boolean>} User object or false
50
62
  */
51
- // eslint-disable-next-line no-unused-vars
52
63
  createUser(user) {
53
64
  this.#error();
54
65
  }
55
66
 
56
67
  /**
57
68
  * Update an existing user.
58
- * @param {*} userId User identifier
59
- * @param {object} user User object
69
+ * @param {string|int} userId User identifier
70
+ * @param {User} user User object
71
+ * @returns {Promise<User>} User object
60
72
  */
61
- // eslint-disable-next-line no-unused-vars
62
73
  updateUser(userId, user) {
63
74
  this.#error();
64
75
  }
65
76
 
66
77
  /**
67
78
  * Delete a user.
68
- * @param {*} userId User identifier
79
+ * @param {string|int} userId User identifier
80
+ * @return {Promise<boolean>} True if successful, false if not
69
81
  */
70
82
  deleteUser(userId) {
71
83
  this.#error();
72
84
  }
85
+
86
+ /**
87
+ * Get all users in the database.
88
+ * @return {Promise<User[]|false>} True if successful, false if not
89
+ */
90
+ getAllUsers() {
91
+ this.#error();
92
+ }
93
+
94
+ /**
95
+ * Close the dao and do any necessary cleanup.
96
+ */
97
+ close() {
98
+ this.#error();
99
+ }
73
100
  }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+ import LowDBUserDao, { createInMemoryLowDb } from "./lowdb-user-dao.js";
5
+ import KnexUserDao, { createBetterSqlite3Db } from "./knex-user-dao.js";
6
+
7
+ describe("user-dao", () => {
8
+ createUserDaoTests("lowdb-user-dao", async () => {
9
+ return new Promise((resolve) => {
10
+ resolve(new LowDBUserDao(createInMemoryLowDb()));
11
+ });
12
+ });
13
+
14
+ createUserDaoTests("knex-user-dao", async () => {
15
+ return new KnexUserDao(await createBetterSqlite3Db());
16
+ });
17
+ });
18
+
19
+ /**
20
+ *
21
+ * @param {String} name Name of the user dao implementation
22
+ * @param {*} createDao A function to create a user dao implementation
23
+ */
24
+ function createUserDaoTests(name, createDao) {
25
+ describe(`${name}`, () => {
26
+ // This is a user that we will reuse in our tests
27
+ const data = {
28
+ username: "test",
29
+ password: "password",
30
+ };
31
+
32
+ /** @type {LowDBUserDao} */
33
+ let dao;
34
+
35
+ beforeEach(async () => {
36
+ // Before each test reset our users database
37
+ dao = await createDao();
38
+ });
39
+
40
+ afterEach(async () => {
41
+ dao.close();
42
+ });
43
+
44
+ it("creates a user", async () => {
45
+ let user = await dao.createUser(data);
46
+
47
+ expect(user.username).toEqual(data.username);
48
+ // The value should be encrypted now
49
+ expect(user.password).not.toEqual(data.password);
50
+ // These fields are added by the create process
51
+ expect(user.verification_token).not.toBeUndefined();
52
+
53
+ const refresh = await dao.getUserByUsername(data.username);
54
+
55
+ // If we retrieve the user by username, it matches the object we got when we created it
56
+ expect(refresh).toMatchObject(user);
57
+ });
58
+
59
+ it("creates a user with an existing username errors", async () => {
60
+ let hasError = false;
61
+
62
+ try {
63
+ // Create the user
64
+ await dao.createUser(data);
65
+ // Attempt to create the user again, this will fail
66
+ await dao.createUser(data);
67
+ } catch (err) {
68
+ hasError = err;
69
+ }
70
+
71
+ expect(hasError).not.toBe(false);
72
+ expect(hasError.message).toEqual("This username is already taken.");
73
+ });
74
+
75
+ it("retrieve user by username and password", async () => {
76
+ const user1 = await dao.createUser(data);
77
+
78
+ const user2 = await dao.getUserByUsernameAndPassword(
79
+ data.username,
80
+ data.password
81
+ );
82
+
83
+ expect(user1).toMatchObject(user2);
84
+ });
85
+
86
+ it("retrieve user by username and wrong password fails", async () => {
87
+ await dao.createUser(data);
88
+
89
+ const user2 = await dao.getUserByUsernameAndPassword(
90
+ data.username,
91
+ "wrong password"
92
+ );
93
+
94
+ expect(user2).toBe(false);
95
+ });
96
+
97
+ it("retrieve user by username and undefined password fails", async () => {
98
+ await dao.createUser(data);
99
+
100
+ const user2 = await dao.getUserByUsernameAndPassword(
101
+ data.username,
102
+ undefined
103
+ );
104
+
105
+ expect(user2).toBe(false);
106
+ });
107
+
108
+ it("retrieve user by username that does not exist fails", async () => {
109
+ const user = await dao.getUserByUsernameAndPassword(
110
+ "notarealuser",
111
+ "password"
112
+ );
113
+
114
+ expect(user).toBe(false);
115
+ });
116
+
117
+ it("retrieve user by username that is undefined fails", async () => {
118
+ const user = await dao.getUserByUsernameAndPassword(
119
+ undefined,
120
+ "password"
121
+ );
122
+
123
+ expect(user).toBe(false);
124
+ });
125
+
126
+ it("updates a user", async () => {
127
+ const user = await dao.createUser(data);
128
+
129
+ expect(user.name).toBeFalsy();
130
+
131
+ const name = "Test User";
132
+
133
+ await dao.updateUser(user.id, { name: name });
134
+
135
+ const reloaded = await dao.getUserById(user.id);
136
+
137
+ expect(reloaded.name).toEqual(name);
138
+ });
139
+
140
+ it("deletes a user by id", async () => {
141
+ const user = await dao.createUser(data);
142
+
143
+ expect(await dao.getAllUsers()).toHaveLength(1);
144
+
145
+ const deleted = await dao.deleteUser(user.id);
146
+
147
+ expect(deleted).toBe(true);
148
+
149
+ const reloaded = await dao.getUserById(user.id);
150
+
151
+ expect(reloaded).toBeUndefined();
152
+
153
+ expect(await dao.getAllUsers()).toHaveLength(0);
154
+ });
155
+ });
156
+ }
package/src/index.js CHANGED
@@ -11,11 +11,12 @@ export {
11
11
  } from "@stanlemon/server";
12
12
  export { default as checkAuth } from "./checkAuth.js";
13
13
  export { default as createAppServer } from "./createAppServer.js";
14
- export { default as schema } from "./schema/user.js";
14
+ export { default as SCHEMAS, PASSWORD, createSchemas } from "./schema/index.js";
15
15
  export { default as SimpleUsersDao } from "./data/simple-users-dao.js";
16
+ export { EVENTS, ROUTES, HIDDEN_FIELDS } from "./constants.js";
16
17
  export {
17
18
  default as LowDBUserDao,
18
- createDb,
19
- createInMemoryDb,
20
- createJsonFileDb,
19
+ createLowDb,
20
+ createInMemoryLowDb,
21
+ createJsonFileLowDb,
21
22
  } from "./data/lowdb-user-dao.js";