@stanlemon/server-with-auth 0.1.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/app.js +25 -0
- package/db.json +21 -0
- package/package.json +35 -0
- package/src/checkAuth.js +16 -0
- package/src/createAppServer.js +99 -0
- package/src/data/simple-users-dao.js +95 -0
- package/src/index.js +15 -0
- package/src/routes/auth.js +143 -0
- package/src/routes/auth.test.js +142 -0
- package/src/schema/user.js +8 -0
- package/test.http +37 -0
package/app.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createAppServer, asyncJsonHandler as handler } from "./src/index.js";
|
|
2
|
+
import SimpleUsersDao from "./src/data/simple-users-dao.js";
|
|
3
|
+
|
|
4
|
+
const users = new SimpleUsersDao();
|
|
5
|
+
|
|
6
|
+
const app = createAppServer({
|
|
7
|
+
port: 3003,
|
|
8
|
+
secure: ["/api/"],
|
|
9
|
+
...users,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
app.get(
|
|
13
|
+
"/",
|
|
14
|
+
handler(({ name }) => ({ hello: "world" }))
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
app.get(
|
|
18
|
+
"/api/users",
|
|
19
|
+
handler(() => ({ users: users.db.data.users }))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
app.get(
|
|
23
|
+
"/insecure",
|
|
24
|
+
handler(() => ({ secure: false }))
|
|
25
|
+
);
|
package/db.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"users": [
|
|
3
|
+
{
|
|
4
|
+
"username": "user",
|
|
5
|
+
"password": "$2a$10$KBUEk19saDPRLKfJYsI8zOgOYGW7Ms1wLGyAwIISjovUKYlnv2qwi",
|
|
6
|
+
"id": "1e56422f-16a4-4508-a779-6a90d685aca1",
|
|
7
|
+
"verification_token": "nUuif_f3l",
|
|
8
|
+
"created_at": "2022-03-06T18:15:51.821Z",
|
|
9
|
+
"last_updated": "2022-03-06T18:34:07.406Z",
|
|
10
|
+
"last_logged_in": "2022-03-06T18:34:07.405Z"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"username": "test123",
|
|
14
|
+
"password": "$2a$10$G/XayivKQZ6dYmHFJS25G.r/GtEiUIrUh/mJCPu/y7UA56czAogMa",
|
|
15
|
+
"id": "2aa34e59-bc71-4ba0-a6c6-67c565667d84",
|
|
16
|
+
"verification_token": "zk_ZAEWKI",
|
|
17
|
+
"created_at": "2022-03-06T18:17:44.052Z",
|
|
18
|
+
"last_updated": "2022-03-06T18:17:44.052Z"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stanlemon/server-with-auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A basic express web server setup with authentication baked in.",
|
|
5
|
+
"author": "Stan Lemon <stanlemon@users.noreply.github.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=17.0"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./src/index.js",
|
|
12
|
+
"exports": "./src/index.js",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "NODE_ENV=development nodemon --ignore db.json ./app.js",
|
|
15
|
+
"lint": "eslint --ext js,jsx,ts,tsx ./",
|
|
16
|
+
"lint:format": "eslint --fix --ext js,jsx,ts,tsx ./",
|
|
17
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles",
|
|
18
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --watch"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@stanlemon/server": "*",
|
|
22
|
+
"bcryptjs": "^2.4.3",
|
|
23
|
+
"jsonwebtoken": "^8.5.1",
|
|
24
|
+
"lowdb": "^3.0.0",
|
|
25
|
+
"passport": "^0.5.2",
|
|
26
|
+
"passport-jwt": "^4.0.0",
|
|
27
|
+
"shortid": "^2.2.8",
|
|
28
|
+
"uuid": "^8.3.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@stanlemon/eslint-config": "*",
|
|
32
|
+
"nodemon": "^2.0.15",
|
|
33
|
+
"supertest": "^6.2.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/checkAuth.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import passport from "passport";
|
|
2
|
+
|
|
3
|
+
export default function checkAuth() {
|
|
4
|
+
return [
|
|
5
|
+
passport.authenticate("jwt", { session: false }),
|
|
6
|
+
(req, res, next) => {
|
|
7
|
+
if (req.isAuthenticated()) {
|
|
8
|
+
next();
|
|
9
|
+
} else {
|
|
10
|
+
res
|
|
11
|
+
.status(401)
|
|
12
|
+
.json({ error: "You must be logged in to access this resource." });
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import {
|
|
3
|
+
createAppServer as createBaseAppServer,
|
|
4
|
+
DEFAULTS as BASE_DEFAULTS,
|
|
5
|
+
} from "@stanlemon/server";
|
|
6
|
+
import passport from "passport";
|
|
7
|
+
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
|
|
8
|
+
import defaultUserSchema from "./schema/user.js";
|
|
9
|
+
import checkAuth from "./checkAuth.js";
|
|
10
|
+
import auth from "./routes/auth.js";
|
|
11
|
+
|
|
12
|
+
dotenv.config();
|
|
13
|
+
|
|
14
|
+
// TODO: Add option for schema
|
|
15
|
+
export const DEFAULTS = {
|
|
16
|
+
...BASE_DEFAULTS,
|
|
17
|
+
secure: [],
|
|
18
|
+
schema: defaultUserSchema,
|
|
19
|
+
getUserById: (userId) => {},
|
|
20
|
+
getUserByUsername: (username) => {},
|
|
21
|
+
getUserByUsernameAndPassword: (username, password) => {},
|
|
22
|
+
getUserByVerificationToken: (token) => {},
|
|
23
|
+
createUser: (user) => {},
|
|
24
|
+
updateUser: (userId, user) => {},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default function createAppServer(options) {
|
|
28
|
+
const {
|
|
29
|
+
port,
|
|
30
|
+
webpack,
|
|
31
|
+
start,
|
|
32
|
+
secure,
|
|
33
|
+
schema,
|
|
34
|
+
getUserById,
|
|
35
|
+
getUserByUsername,
|
|
36
|
+
getUserByUsernameAndPassword,
|
|
37
|
+
getUserByVerificationToken,
|
|
38
|
+
createUser,
|
|
39
|
+
updateUser,
|
|
40
|
+
} = { ...DEFAULTS, ...options };
|
|
41
|
+
|
|
42
|
+
if (!process.env.JWT_SECRET) {
|
|
43
|
+
console.warn("You need to specify a secret.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const secret = process.env.JWT_SECRET || "YouNeedASecret";
|
|
47
|
+
|
|
48
|
+
const app = createBaseAppServer({ port, webpack, start });
|
|
49
|
+
|
|
50
|
+
passport.use(
|
|
51
|
+
"jwt",
|
|
52
|
+
new JwtStrategy(
|
|
53
|
+
{
|
|
54
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
55
|
+
secretOrKey: secret,
|
|
56
|
+
// NOTE: Setting options like 'issuer' here must also be set when the token is signed below
|
|
57
|
+
jsonWebTokenOptions: {
|
|
58
|
+
expiresIn: "120m",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
(payload, done) => {
|
|
62
|
+
done(null, payload);
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
passport.serializeUser((id, done) => {
|
|
67
|
+
done(null, id);
|
|
68
|
+
});
|
|
69
|
+
passport.deserializeUser((id, done) => {
|
|
70
|
+
getUserById(id)
|
|
71
|
+
.then((user) => {
|
|
72
|
+
// An undefined user means we couldn't find it, so the session is invalid
|
|
73
|
+
done(null, user === undefined ? false : user);
|
|
74
|
+
})
|
|
75
|
+
.catch((error) => {
|
|
76
|
+
done(error, null);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
passport.initialize();
|
|
80
|
+
|
|
81
|
+
app.use(
|
|
82
|
+
auth({
|
|
83
|
+
secret,
|
|
84
|
+
schema,
|
|
85
|
+
getUserById,
|
|
86
|
+
getUserByUsername,
|
|
87
|
+
getUserByUsernameAndPassword,
|
|
88
|
+
getUserByVerificationToken,
|
|
89
|
+
createUser,
|
|
90
|
+
updateUser,
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
secure.forEach((path) => {
|
|
95
|
+
app.use(path, checkAuth());
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return app;
|
|
99
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Low, JSONFile } from "lowdb";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import shortid from "shortid";
|
|
4
|
+
import bcrypt from "bcryptjs";
|
|
5
|
+
|
|
6
|
+
export default class SimpleUsersDao {
|
|
7
|
+
constructor(seeds = [], adapter = new JSONFile("./db.json")) {
|
|
8
|
+
this.db = new Low(adapter);
|
|
9
|
+
|
|
10
|
+
this.db.read().then(() => {
|
|
11
|
+
this.db.data ||= { users: [] };
|
|
12
|
+
|
|
13
|
+
if (seeds.length > 0) {
|
|
14
|
+
seeds.forEach((user) => this.createUser(user));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getUserById = (userId) => {
|
|
20
|
+
return this.db.data.users
|
|
21
|
+
.filter((user) => {
|
|
22
|
+
// This one can get gross with numerical ids
|
|
23
|
+
// eslint-disable-next-line eqeqeq
|
|
24
|
+
return user.id == userId;
|
|
25
|
+
})
|
|
26
|
+
.shift();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
getUserByUsername = (username) => {
|
|
30
|
+
return this.db.data.users
|
|
31
|
+
.filter((user) => {
|
|
32
|
+
return user.username === username;
|
|
33
|
+
})
|
|
34
|
+
.shift();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
getUserByUsernameAndPassword = (username, password) => {
|
|
38
|
+
const user = this.getUserByUsername(username);
|
|
39
|
+
|
|
40
|
+
if (!bcrypt.compareSync(password, user.password)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return user;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
getUserByVerificationToken = (token) => {
|
|
48
|
+
return this.db.data.users
|
|
49
|
+
.filter((user) => user.verification_token === token)
|
|
50
|
+
.shift();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
createUser = async (user) => {
|
|
54
|
+
const existing = this.getUserByUsername(user.username);
|
|
55
|
+
|
|
56
|
+
if (existing) {
|
|
57
|
+
throw new Error("This username is already taken.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const now = new Date();
|
|
61
|
+
const data = {
|
|
62
|
+
...user,
|
|
63
|
+
password: bcrypt.hashSync(user.password, 10),
|
|
64
|
+
id: uuidv4(),
|
|
65
|
+
verification_token: shortid.generate(),
|
|
66
|
+
created_at: now,
|
|
67
|
+
last_updated: now,
|
|
68
|
+
};
|
|
69
|
+
this.db.data.users.push(data);
|
|
70
|
+
await this.db.write();
|
|
71
|
+
return data;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
updateUser = async (userId, user) => {
|
|
75
|
+
const now = new Date();
|
|
76
|
+
this.db.data.users = this.db.data.users.map((u) => {
|
|
77
|
+
if (u.id === userId) {
|
|
78
|
+
// If the password has been set, encrypt it
|
|
79
|
+
if (user.password) {
|
|
80
|
+
user.password = bcrypt.hashSync(user.password, 10);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...u,
|
|
85
|
+
...user,
|
|
86
|
+
id: userId,
|
|
87
|
+
last_updated: now,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return u;
|
|
91
|
+
});
|
|
92
|
+
await this.db.write();
|
|
93
|
+
return this.getUserById(userId);
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
convertCase,
|
|
3
|
+
formatInput,
|
|
4
|
+
formatOutput,
|
|
5
|
+
asyncJsonHandler,
|
|
6
|
+
schemaHandler,
|
|
7
|
+
BadRequestException,
|
|
8
|
+
NotAuthorizedException,
|
|
9
|
+
NotFoundException,
|
|
10
|
+
AlreadyExistsException,
|
|
11
|
+
} from "@stanlemon/server";
|
|
12
|
+
export { default as checkAuth } from "./checkAuth.js";
|
|
13
|
+
export { default as createAppServer } from "./createAppServer.js";
|
|
14
|
+
export { default as schema } from "./schema/user.js";
|
|
15
|
+
export { default as SimpleUsersDao } from "./data/simple-users-dao.js";
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { isEmpty } from "lodash-es";
|
|
2
|
+
import { Router } from "express";
|
|
3
|
+
import passport from "passport";
|
|
4
|
+
import jwt from "jsonwebtoken";
|
|
5
|
+
import {
|
|
6
|
+
schemaHandler,
|
|
7
|
+
formatOutput,
|
|
8
|
+
BadRequestException,
|
|
9
|
+
} from "@stanlemon/server";
|
|
10
|
+
|
|
11
|
+
/* eslint-disable max-lines-per-function */
|
|
12
|
+
export default function authRoutes({
|
|
13
|
+
secret,
|
|
14
|
+
schema,
|
|
15
|
+
getUserById,
|
|
16
|
+
getUserByUsername,
|
|
17
|
+
getUserByUsernameAndPassword,
|
|
18
|
+
getUserByVerificationToken,
|
|
19
|
+
createUser,
|
|
20
|
+
updateUser,
|
|
21
|
+
}) {
|
|
22
|
+
const router = Router();
|
|
23
|
+
|
|
24
|
+
router.get("/auth/session", (req, res, next) => {
|
|
25
|
+
/* look at the 2nd parameter to the below call */
|
|
26
|
+
passport.authenticate("jwt", { session: false }, (err, userId) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
return next(err);
|
|
29
|
+
}
|
|
30
|
+
if (!userId) {
|
|
31
|
+
return res.status(401).json({
|
|
32
|
+
token: false,
|
|
33
|
+
user: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
req.logIn(userId, async (err) => {
|
|
38
|
+
if (err) {
|
|
39
|
+
return next(err);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const user = await getUserById(userId);
|
|
43
|
+
|
|
44
|
+
if (!user) {
|
|
45
|
+
return res.status(401).json({
|
|
46
|
+
token: false,
|
|
47
|
+
user: false,
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
const token = jwt.sign(user.id, secret);
|
|
51
|
+
res
|
|
52
|
+
.status(200)
|
|
53
|
+
.json({ token, user: formatOutput(user, ["password"]) });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
})(req, res, next);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
router.post("/auth/login", async (req, res) => {
|
|
60
|
+
const user = await getUserByUsernameAndPassword(
|
|
61
|
+
req.body.username,
|
|
62
|
+
req.body.password
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (!user) {
|
|
66
|
+
res.status(401).json({
|
|
67
|
+
message: "Incorrect username or password.",
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const update = await updateUser(user.id, {
|
|
73
|
+
last_logged_in: new Date(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const token = jwt.sign(user.id, secret);
|
|
77
|
+
|
|
78
|
+
res.json({
|
|
79
|
+
token,
|
|
80
|
+
user: formatOutput(update, ["password"]),
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
router.get("/auth/logout", (req, res) => {
|
|
85
|
+
req.logout();
|
|
86
|
+
|
|
87
|
+
return res.status(401).json({
|
|
88
|
+
token: false,
|
|
89
|
+
user: false,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
router.post(
|
|
94
|
+
"/auth/register",
|
|
95
|
+
schemaHandler(schema, async (data) => {
|
|
96
|
+
const existing = await getUserByUsername(data.username);
|
|
97
|
+
|
|
98
|
+
if (existing) {
|
|
99
|
+
throw new BadRequestException(
|
|
100
|
+
"A user with this username already exists"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const user = await createUser(data);
|
|
105
|
+
|
|
106
|
+
if (isEmpty(user)) {
|
|
107
|
+
return {
|
|
108
|
+
message: "An error has occurred",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// TODO: Add hook for handling verification notification
|
|
113
|
+
// user.verification_token
|
|
114
|
+
|
|
115
|
+
const token = jwt.sign(user.id, secret);
|
|
116
|
+
return { token, user: formatOutput(user, ["password"]) };
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
router.get("/auth/verify/:token", async (req, res) => {
|
|
121
|
+
const { token } = req.params;
|
|
122
|
+
|
|
123
|
+
const user = await getUserByVerificationToken(token);
|
|
124
|
+
|
|
125
|
+
if (isEmpty(user)) {
|
|
126
|
+
return res
|
|
127
|
+
.status(400)
|
|
128
|
+
.json({ success: false, message: "Cannot verify user." });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (user.verified_date) {
|
|
132
|
+
return res
|
|
133
|
+
.status(400)
|
|
134
|
+
.send({ success: false, message: "User already verified." });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await updateUser(user.id, { verified_date: new Date() });
|
|
138
|
+
|
|
139
|
+
return res.send({ success: true, message: "User verified!" });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return router;
|
|
143
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import request from "supertest";
|
|
2
|
+
import { Memory } from "lowdb";
|
|
3
|
+
import createAppServer from "../createAppServer";
|
|
4
|
+
import SimpleUsersDao from "../data/simple-users-dao.js";
|
|
5
|
+
|
|
6
|
+
let users = new SimpleUsersDao([], new Memory());
|
|
7
|
+
|
|
8
|
+
const app = createAppServer({ ...users, start: false });
|
|
9
|
+
|
|
10
|
+
describe("/auth", () => {
|
|
11
|
+
let userId;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
// Reset our users database before each test
|
|
15
|
+
const user = await users.createUser({
|
|
16
|
+
username: "test",
|
|
17
|
+
password: "test",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
userId = user.id;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Disabling this linting rule because it is unaware of the supertest assertions
|
|
24
|
+
/* eslint-disable jest/expect-expect */
|
|
25
|
+
it("POST /register creates a user", async () => {
|
|
26
|
+
const username = "test1";
|
|
27
|
+
const password = "p@$$w0rd!";
|
|
28
|
+
|
|
29
|
+
const data = {
|
|
30
|
+
username,
|
|
31
|
+
password,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
await request(app)
|
|
35
|
+
.post("/auth/register")
|
|
36
|
+
.set("Content-Type", "application/json")
|
|
37
|
+
.set("Accept", "application/json")
|
|
38
|
+
.send(data)
|
|
39
|
+
.expect(200)
|
|
40
|
+
.then((res) => {
|
|
41
|
+
expect(res.body.errors).toBeUndefined();
|
|
42
|
+
expect(res.body.token).not.toBeUndefined();
|
|
43
|
+
expect(res.body.user).not.toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("POST /register returns error on empty data", async () => {
|
|
48
|
+
await request(app)
|
|
49
|
+
.post("/auth/register")
|
|
50
|
+
.set("Content-Type", "application/json")
|
|
51
|
+
.set("Accept", "application/json")
|
|
52
|
+
.expect(400)
|
|
53
|
+
.then((res) => {
|
|
54
|
+
expect(res.body.errors).not.toBe(undefined);
|
|
55
|
+
expect(res.body.errors.username).not.toBe(undefined);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("POST /register returns error on short password", async () => {
|
|
60
|
+
await request(app)
|
|
61
|
+
.post("/auth/register")
|
|
62
|
+
.send({
|
|
63
|
+
username: "testshort",
|
|
64
|
+
password: "short",
|
|
65
|
+
})
|
|
66
|
+
.set("Content-Type", "application/json")
|
|
67
|
+
.set("Accept", "application/json")
|
|
68
|
+
.expect(400)
|
|
69
|
+
.then((res) => {
|
|
70
|
+
expect(res.body.errors).not.toBe(undefined);
|
|
71
|
+
expect(res.body.errors.password).not.toBe(undefined);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("POST /register returns error on too long password", async () => {
|
|
76
|
+
await request(app)
|
|
77
|
+
.post("/auth/register")
|
|
78
|
+
.send({
|
|
79
|
+
username: "testlong",
|
|
80
|
+
password:
|
|
81
|
+
"waytolongpasswordtobeusedforthisapplicationyoushouldtrysomethingmuchmuchshorter",
|
|
82
|
+
})
|
|
83
|
+
.set("Content-Type", "application/json")
|
|
84
|
+
.set("Accept", "application/json")
|
|
85
|
+
.expect(400)
|
|
86
|
+
.then((res) => {
|
|
87
|
+
expect(res.body.errors).not.toBe(undefined);
|
|
88
|
+
expect(res.body.errors.password).not.toBe(undefined);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("POST /register returns error on already taken username", async () => {
|
|
93
|
+
await request(app)
|
|
94
|
+
.post("/auth/register")
|
|
95
|
+
.send({
|
|
96
|
+
username: "test",
|
|
97
|
+
password: "p@$$w0rd!",
|
|
98
|
+
})
|
|
99
|
+
.set("Content-Type", "application/json")
|
|
100
|
+
.set("Accept", "application/json")
|
|
101
|
+
.expect(400)
|
|
102
|
+
.then((res) => {
|
|
103
|
+
expect(res.body).toEqual({
|
|
104
|
+
error: "Bad Request: A user with this username already exists",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("GET /verify verifies user", async () => {
|
|
110
|
+
const user = users.getUserById(userId);
|
|
111
|
+
|
|
112
|
+
expect(user.verification_token).not.toBe(null);
|
|
113
|
+
expect(user.verified_date).toBeUndefined();
|
|
114
|
+
|
|
115
|
+
// First call will verify the user
|
|
116
|
+
await request(app)
|
|
117
|
+
.get("/auth/verify/" + user.verification_token)
|
|
118
|
+
.set("Content-Type", "application/json")
|
|
119
|
+
.set("Accept", "application/json")
|
|
120
|
+
.expect(200)
|
|
121
|
+
.then((res) => {
|
|
122
|
+
expect(res.body.success).toEqual(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const refresh = await users.getUserById(userId);
|
|
126
|
+
|
|
127
|
+
expect(refresh.verified_date).not.toBe(null);
|
|
128
|
+
|
|
129
|
+
// Subsequent calls are not successful because the user is already verified
|
|
130
|
+
await request(app)
|
|
131
|
+
.get("/auth/verify/" + user.verification_token)
|
|
132
|
+
.set("Content-Type", "application/json")
|
|
133
|
+
.set("Accept", "application/json")
|
|
134
|
+
.expect(400)
|
|
135
|
+
.then((res) => {
|
|
136
|
+
expect(res.body).toEqual({
|
|
137
|
+
success: false,
|
|
138
|
+
message: "User already verified.",
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
package/test.http
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
GET http://localhost:3003/ HTTP/1.1
|
|
2
|
+
content-type: application/json
|
|
3
|
+
|
|
4
|
+
###
|
|
5
|
+
POST http://localhost:3003/auth/register HTTP/1.1
|
|
6
|
+
Content-Type: application/json
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
"name": "Test Example",
|
|
10
|
+
"email": "example@example.com",
|
|
11
|
+
"username": "test123",
|
|
12
|
+
"password": "password"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
###
|
|
16
|
+
|
|
17
|
+
# @name login
|
|
18
|
+
POST http://localhost:3003/auth/login HTTP/1.1
|
|
19
|
+
Content-Type: application/json
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"username": "user",
|
|
23
|
+
"password": "password"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
###
|
|
27
|
+
|
|
28
|
+
@authToken = {{login.response.body.token}}
|
|
29
|
+
|
|
30
|
+
# @name session
|
|
31
|
+
GET http://localhost:3003/auth/session
|
|
32
|
+
Authorization: Bearer {{authToken}}
|
|
33
|
+
|
|
34
|
+
###
|
|
35
|
+
|
|
36
|
+
GET http://localhost:3003/api/users
|
|
37
|
+
Authorization: Bearer {{authToken}}
|