Step 4
Let's start refactoring the app so the tests pass.
First, add the following helper method to server/util/token.js
:
const parseBearer = (bearer) => {
const [_, token] = bearer.trim().split(" ");
return token;
};
Make sure to update the export statement:
module.exports = {
createToken,
verifyToken,
decodeToken,
+ parseBearer
};
Then, update the chekAdmin
middleware in server/routes/users.js
:
- const { verifyToken, decodeToken } = require("../util/token");
+ const { verifyToken, decodeToken, parseBearer } = require("../util/token");
const checkAdmin = async (req, res, next) => {
const { authorization } = req.headers;
- const [_, token] = authorization.trim().split(" ");
+ const token = authorization ? parseBearer(authorization) : "";
const valid = await verifyToken(token);
const user = decodeToken(token);
if (!valid || user.role !== "ADMIN") {
return res.status(403).json({
message:
"You are not authorized to access this resource.",
});
}
next();
};
The updates above will ensure our server will not crash if an authorization token was not provided.
Let's make a few more updates! When I was writing the tests, I decided to slightly modify the behaviour of the API. For example, I decided to return an empty array when a client sends a HTTP GET request to api/users?username={{some-username}}
when n user matches the {{some-username}}
query. Therefore, we must update the readOne
operation in server/data/UserDao.js
as follows:
- // returns null if no user matches the search query
+ // returns empty array if no user matches the username
async readOne(username) {
- const user = await User.findOne({ username });
+ const user = await User.find({ username });
return user;
}
Moreover, I decided to return $404$ when a client sends a HTTP GET request to api/users/:id
with an ID parameter that does not match any of the users. Therefore, we must update the read
operation in server/data/UserDao.js
as follows:
// returns an empty array if there is no user with the given ID
async read(id) {
const user = await User.findById(id);
- return user ? user : [];
+ if (user === null) {
+ return res.status(400).json({ message: "You must provide at least one user attribute!" });
+ }
+ return user;
}
We should also update the /api/users/:id
handler as follows:
router.get("/api/users/:id", checkAdmin, async (req, res) => {
+ try {
const { id } = req.params;
const data = await users.read(id);
res.json({ data: data ? data : [] });
+ } catch (err) {
+ res.status(err.status).json({ message: err.message });
+ }
});
While at it, let's also update the /api/users/:id
handler for PUT request to capture the edge case of a request without a payload:
router.put("/api/users/:id", checkAdmin, async (req, res) => {
try {
const { id } = req.params;
const { password, role } = req.body;
+ if (!password && !role) {
+ throw new ApiError(400, "You must provide at least one user attribute!");
+ }
const data = await users.update(id, { password, role });
res.json({ data });
} catch (err) {
res.status(err.status).json({ message: err.message });
}
});
Save all changes and run the tests. Except for a handful, all other tests must pass.