@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Joi from "joi";
|
|
2
|
+
import { ROUTES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema for a strong password.
|
|
6
|
+
*/
|
|
7
|
+
export const PASSWORD = Joi.string()
|
|
8
|
+
.required()
|
|
9
|
+
.min(8)
|
|
10
|
+
.max(64)
|
|
11
|
+
.label("Password");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Schema for creating a new user
|
|
15
|
+
*/
|
|
16
|
+
export const CREATE_USER = Joi.object({
|
|
17
|
+
username: Joi.string().required().label("Username"),
|
|
18
|
+
password: PASSWORD,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const RESET_PASSWORD = Joi.object({
|
|
22
|
+
username: Joi.string().required().label("Username"),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const CHANGE_PASSWORD = Joi.object({
|
|
26
|
+
password: PASSWORD,
|
|
27
|
+
current_password: PASSWORD.label("Current Password"),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const USER = Joi.object({});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* For use by the DAO to make sure the data shape is correct.
|
|
34
|
+
*/
|
|
35
|
+
export const DAO = Joi.object({
|
|
36
|
+
id: Joi.string().uuid().label("ID").default("").required(),
|
|
37
|
+
username: Joi.string().label("Username").default("").required(),
|
|
38
|
+
password: Joi.string().label("Password").default("").required(),
|
|
39
|
+
verification_token: Joi.string().label("Verification Token").default(""),
|
|
40
|
+
verify_at: Joi.date().label("Date Verified").default(null),
|
|
41
|
+
last_logged_in: Joi.date().label("Date Last Logged In").default(null),
|
|
42
|
+
created_at: Joi.date().label("Date Created").default(null).required(),
|
|
43
|
+
last_updated: Joi.date().label("Date Last Updated").default(null).required(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Base schemas mapped to routes.
|
|
48
|
+
*/
|
|
49
|
+
const SCHEMAS = {
|
|
50
|
+
[ROUTES.SIGNUP]: CREATE_USER,
|
|
51
|
+
[ROUTES.RESET]: RESET_PASSWORD,
|
|
52
|
+
[ROUTES.PASSWORD]: CHANGE_PASSWORD,
|
|
53
|
+
[ROUTES.USER]: USER,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default SCHEMAS;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a collection of schemas while supplying a custom user schema.
|
|
60
|
+
* @param {Joi.Schema} user User schema to append to the base schemas for sign up and user operations.
|
|
61
|
+
* @returns {Object.<string, Joi.Schema>} Schemas by end point.
|
|
62
|
+
*/
|
|
63
|
+
export function createSchemas(user = {}) {
|
|
64
|
+
return {
|
|
65
|
+
...SCHEMAS,
|
|
66
|
+
[ROUTES.SIGNUP]: SCHEMAS[[ROUTES.SIGNUP]].append(user),
|
|
67
|
+
[ROUTES.USER]: SCHEMAS[[ROUTES.USER]].append(user),
|
|
68
|
+
dao: DAO.append(user),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Joi from "joi";
|
|
2
|
+
import { ROUTES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export default function checkSchemas(schemas) {
|
|
5
|
+
Object.values(schemas).forEach((schema) => {
|
|
6
|
+
if (!Joi.isSchema(schema)) {
|
|
7
|
+
throw new Error("The schema object must be of type Joi schema.");
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
if (schemas[ROUTES.SIGNUP] === undefined) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"The schemas object must have a schema designed for the signup route."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
!schemas[ROUTES.SIGNUP].describe().keys.username ||
|
|
19
|
+
!schemas[ROUTES.SIGNUP].describe().keys.password
|
|
20
|
+
) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"The schema object for the signup route must have a username and password defined."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/test.http
CHANGED
|
@@ -2,11 +2,13 @@ GET http://localhost:3003/ HTTP/1.1
|
|
|
2
2
|
content-type: application/json
|
|
3
3
|
|
|
4
4
|
###
|
|
5
|
-
|
|
5
|
+
# @name signup
|
|
6
|
+
|
|
7
|
+
POST http://localhost:3003/auth/signup HTTP/1.1
|
|
6
8
|
Content-Type: application/json
|
|
7
9
|
|
|
8
10
|
{
|
|
9
|
-
"
|
|
11
|
+
"fullName": "Test Example",
|
|
10
12
|
"email": "example@example.com",
|
|
11
13
|
"username": "test123",
|
|
12
14
|
"password": "password"
|
|
@@ -19,7 +21,7 @@ POST http://localhost:3003/auth/login HTTP/1.1
|
|
|
19
21
|
Content-Type: application/json
|
|
20
22
|
|
|
21
23
|
{
|
|
22
|
-
"username": "
|
|
24
|
+
"username": "test123",
|
|
23
25
|
"password": "password"
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -27,11 +29,67 @@ Content-Type: application/json
|
|
|
27
29
|
|
|
28
30
|
@authToken = {{login.response.body.token}}
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
###
|
|
33
|
+
|
|
31
34
|
GET http://localhost:3003/auth/session
|
|
32
35
|
Authorization: Bearer {{authToken}}
|
|
33
36
|
|
|
34
37
|
###
|
|
35
38
|
|
|
39
|
+
GET http://localhost:3003/auth/user
|
|
40
|
+
Authorization: Bearer {{authToken}}
|
|
41
|
+
|
|
42
|
+
###
|
|
43
|
+
|
|
36
44
|
GET http://localhost:3003/api/users
|
|
37
45
|
Authorization: Bearer {{authToken}}
|
|
46
|
+
|
|
47
|
+
###
|
|
48
|
+
|
|
49
|
+
PUT http://localhost:3003/auth/user HTTP/1.1
|
|
50
|
+
Authorization: Bearer {{authToken}}
|
|
51
|
+
Content-Type: application/json
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
"email": "test@test.com",
|
|
55
|
+
"fullName": "Stanley Lemon"
|
|
56
|
+
}
|
|
57
|
+
###
|
|
58
|
+
|
|
59
|
+
POST http://localhost:3003/auth/reset HTTP/1.1
|
|
60
|
+
Authorization: Bearer {{authToken}}
|
|
61
|
+
Content-Type: application/json
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
"username": "test123"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
###
|
|
68
|
+
|
|
69
|
+
POST http://localhost:3003/auth/password HTTP/1.1
|
|
70
|
+
Authorization: Bearer {{authToken}}
|
|
71
|
+
Content-Type: application/json
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
"currentPassword": "password",
|
|
75
|
+
"password": "newPassw0rd"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
###
|
|
79
|
+
|
|
80
|
+
DELETE http://localhost:3003/auth/user HTTP/1.1
|
|
81
|
+
Authorization: Bearer {{authToken}}
|
|
82
|
+
|
|
83
|
+
###
|
|
84
|
+
|
|
85
|
+
GET http://localhost:3003/auth/logout
|
|
86
|
+
Authorization: Bearer {{authToken}}
|
|
87
|
+
|
|
88
|
+
###
|
|
89
|
+
|
|
90
|
+
GET http://localhost:3003/doesNotExist
|
|
91
|
+
|
|
92
|
+
###
|
|
93
|
+
|
|
94
|
+
GET http://localhost:3003/api/doesNotExist
|
|
95
|
+
Authorization: Bearer {{authToken}}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment node
|
|
3
|
-
*/
|
|
4
|
-
import LowDBUserDao, { createInMemoryDb } from "./lowdb-user-dao.js";
|
|
5
|
-
|
|
6
|
-
describe("lowdb-user-dao", () => {
|
|
7
|
-
// This is a user that we will reuse in our tests
|
|
8
|
-
const data = {
|
|
9
|
-
username: "test",
|
|
10
|
-
password: "password",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/** @type {LowSync} */
|
|
14
|
-
let db;
|
|
15
|
-
/** @type {LowDBUserDao} */
|
|
16
|
-
let dao;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
// Before each test reset our users database
|
|
20
|
-
db = createInMemoryDb();
|
|
21
|
-
dao = new LowDBUserDao(db);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("creates a user", async () => {
|
|
25
|
-
let user = await dao.createUser(data);
|
|
26
|
-
|
|
27
|
-
expect(user.username).toEqual(data.username);
|
|
28
|
-
// The value should be encrypted now
|
|
29
|
-
expect(user.password).not.toEqual(data.password);
|
|
30
|
-
// These fields are added by the create process
|
|
31
|
-
expect(user.verification_token).not.toBeUndefined();
|
|
32
|
-
expect(user.created_at).not.toBeUndefined();
|
|
33
|
-
expect(user.last_updated).not.toBeUndefined();
|
|
34
|
-
|
|
35
|
-
const refresh = dao.getUserByUsername(data.username);
|
|
36
|
-
|
|
37
|
-
// If we retrieve the user by username, it matches the object we got when we created it
|
|
38
|
-
expect(refresh).toMatchObject(user);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("creates a user with an existing username errors", async () => {
|
|
42
|
-
let hasError = false;
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
// Create the user
|
|
46
|
-
await dao.createUser(data);
|
|
47
|
-
// Attempt to create the user again, this will fail
|
|
48
|
-
await dao.createUser(data);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
hasError = err;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
expect(hasError).not.toBe(false);
|
|
54
|
-
expect(hasError.message).toEqual("This username is already taken.");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("retrieve user by username and password", async () => {
|
|
58
|
-
const user1 = await dao.createUser(data);
|
|
59
|
-
|
|
60
|
-
const user2 = dao.getUserByUsernameAndPassword(
|
|
61
|
-
data.username,
|
|
62
|
-
data.password
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
expect(user1).toMatchObject(user2);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("retrieve user by username and wrong password fails", async () => {
|
|
69
|
-
await dao.createUser(data);
|
|
70
|
-
|
|
71
|
-
const user2 = dao.getUserByUsernameAndPassword(
|
|
72
|
-
data.username,
|
|
73
|
-
"wrong password"
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
expect(user2).toBe(false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("retrieve user by username and undefined password fails", async () => {
|
|
80
|
-
await dao.createUser(data);
|
|
81
|
-
|
|
82
|
-
const user2 = dao.getUserByUsernameAndPassword(data.username, undefined);
|
|
83
|
-
|
|
84
|
-
expect(user2).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("retrieve user by username that does not exist fails", async () => {
|
|
88
|
-
const user = dao.getUserByUsernameAndPassword("notarealuser", "password");
|
|
89
|
-
|
|
90
|
-
expect(user).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("retrieve user by username that is undefined fails", async () => {
|
|
94
|
-
const user = dao.getUserByUsernameAndPassword(undefined, "password");
|
|
95
|
-
|
|
96
|
-
expect(user).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("deletes a user by id", async () => {
|
|
100
|
-
const user = await dao.createUser(data);
|
|
101
|
-
|
|
102
|
-
expect(db.data.users).toHaveLength(1);
|
|
103
|
-
|
|
104
|
-
const deleted = dao.deleteUser(user.id);
|
|
105
|
-
|
|
106
|
-
expect(deleted).toBe(true);
|
|
107
|
-
expect(dao.getUserById(user.id)).toBeUndefined();
|
|
108
|
-
|
|
109
|
-
expect(db.data.users).toHaveLength(0);
|
|
110
|
-
});
|
|
111
|
-
});
|