@stanlemon/server-with-auth 0.2.11 → 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.
- package/README.md +48 -0
- package/app.js +37 -4
- package/package.json +8 -4
- package/src/constants.js +59 -0
- package/src/createAppServer.js +71 -29
- package/src/data/knex-user-dao.js +142 -0
- package/src/data/lowdb-user-dao.js +79 -49
- package/src/data/user-dao.js +38 -11
- package/src/data/user-dao.test.js +156 -0
- package/src/index.js +5 -4
- package/src/routes/auth.js +221 -48
- package/src/routes/auth.test.js +204 -49
- package/src/schema/index.js +70 -0
- package/src/utilities/checkSchemas.js +25 -0
- package/src/utilities/checkUserDao.js +7 -0
- package/test.http +62 -4
- package/src/data/lowdb-user-dao.test.js +0 -111
- package/src/schema/user.js +0 -8
|
@@ -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
|
|
7
|
+
export function createInMemoryLowDb() {
|
|
8
8
|
return new LowSync(new MemorySync(), {});
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function
|
|
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
|
|
20
|
+
export function createLowDb() {
|
|
21
21
|
return process.env.NODE_ENV === "test"
|
|
22
|
-
?
|
|
23
|
-
:
|
|
22
|
+
? createInMemoryLowDb()
|
|
23
|
+
: createJsonFileLowDb();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export default class LowDBUserDao extends UserDao {
|
|
27
27
|
#db;
|
|
28
28
|
|
|
29
|
-
constructor(db =
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
101
|
+
return this.getUserByUsername(user.username).then((existing) => {
|
|
102
|
+
if (existing) {
|
|
103
|
+
throw new Error("This username is already taken.");
|
|
104
|
+
}
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
throw new Error("This username is already taken.");
|
|
92
|
-
}
|
|
106
|
+
const now = new Date();
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/src/data/user-dao.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
59
|
-
* @param {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
createLowDb,
|
|
20
|
+
createInMemoryLowDb,
|
|
21
|
+
createJsonFileLowDb,
|
|
21
22
|
} from "./data/lowdb-user-dao.js";
|