Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 66 additions & 8 deletions api/auth/auth-middleware.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const { JWT_SECRET } = require("../secrets"); // use this secret!
const jwt = require("jsonwebtoken");
const { JWT_SECRET } = require("./../secrets"); // use this secret!
const User = require("./../users/users-model");
const yup = require("yup");

const restricted = (req, res, next) => {
/*
Expand All @@ -16,7 +19,25 @@ const restricted = (req, res, next) => {

Put the decoded token in the req object, to make life easier for middlewares downstream!
*/
}
const token = req.headers.authorization;
if (!token) {
return next({
status: 401,
message: "Token required"
});
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return next({
status: 401,
message: "Token invalid"
});
}
req.decodedJwt = decoded;
next();
})
};


const only = role_name => (req, res, next) => {
/*
Expand All @@ -29,21 +50,39 @@ const only = role_name => (req, res, next) => {

Pull the decoded token from the req object, to avoid verifying it again!
*/
}
if (req.decodedJwt.role_name !== role_name) {
next({
status: 403,
message: "This is not for you"
});
} else {
next();
}
};


const checkUsernameExists = (req, res, next) => {
const checkUsernameExists = async (req, res, next) => {
/*
If the username in req.body does NOT exist in the database
status 401
{
"message": "Invalid credentials"
}
*/
}
const { username } = req.body;
const user = await User.findBy({ username });
if (!user) {
next({
status: 401,
message: "Invalid credentials"
});
} else {
next();
}
};


const validateRoleName = (req, res, next) => {
const validateRoleName = async (req, res, next) => {
/*
If the role_name in the body is valid, set req.role_name to be the trimmed string and proceed.

Expand All @@ -62,11 +101,30 @@ const validateRoleName = (req, res, next) => {
"message": "Role name can not be longer than 32 chars"
}
*/
}
const roleSchema = yup.object().shape({
role_name: yup
.string()
.trim()
.notOneOf(["admin"], "Role name can not be admin")
.max(32, "Role name can not be longer than 32 chars")
.default(() => { return "student" })
})
try {
const { role_name } = req.body;
const validatedRole = await roleSchema.validate({ role_name })
req.role_name = validatedRole.role_name;
next()
} catch (err) {
next({
status: 422,
message: err.errors[0]
})
}
};

module.exports = {
restricted,
checkUsernameExists,
validateRoleName,
only,
}
};
107 changes: 75 additions & 32 deletions api/auth/auth-router.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,85 @@
const router = require("express").Router();
const { checkUsernameExists, validateRoleName } = require('./auth-middleware');
const { JWT_SECRET } = require("../secrets"); // use this secret!

router.post("/register", validateRoleName, (req, res, next) => {
/**
[POST] /api/auth/register { "username": "anna", "password": "1234", "role_name": "angel" }

response:
status 201
{
"user"_id: 3,
"username": "anna",
"role_name": "angel"
}
*/
});
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const {
checkUsernameExists,
validateRoleName
} = require('./auth-middleware');
const User = require("./../users/users-model");
const { BCRYPT_ROUNDS } = require("./../../config");
const buildToken = require("./token-builder");

router.post("/register",
validateRoleName,
async (req, res, next) => {
/**
[POST] /api/auth/register { "username": "anna", "password": "1234", "role_name": "angel" }

response:
status 201
{
"user"_id: 3,
"username": "anna",
"role_name": "angel"
}
*/
try {
let user = req.body;
const hash = bcrypt.hashSync(user.password, BCRYPT_ROUNDS);
user.role_name = req.role_name;
user.password = hash;

router.post("/login", checkUsernameExists, (req, res, next) => {
/**
[POST] /api/auth/login { "username": "sue", "password": "1234" }
const newUser = await User.add(user);

response:
status 200
{
"message": "sue is back!",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ETC.ETC"
res.status(201).json(newUser);
} catch (err) {
next(err);
}
}
);


router.post("/login",
checkUsernameExists,
async (req, res, next) => {
/**
[POST] /api/auth/login { "username": "sue", "password": "1234" }

response:
status 200
{
"message": "sue is back!",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ETC.ETC"
}

The token must expire in one day, and must provide the following information
in its payload:
The token must expire in one day, and must provide the following information
in its payload:

{
"subject" : 1 // the user_id of the authenticated user
"username" : "bob" // the username of the authenticated user
"role_name": "admin" // the role of the authenticated user
{
"subject" : 1 // the user_id of the authenticated user
"username" : "bob" // the username of the authenticated user
"role_name": "admin" // the role of the authenticated user
}
*/
try {
const credentials = req.body;
const user = await User.findBy({ username: credentials.username });
if (user[0] && bcrypt.compareSync(credentials.password, user[0].password)) {
const token = buildToken(user[0])
res.status(200).json({
message: `${credentials.username} is back!`,
token: token
});
} else {
next({
status: 401,
message: "Invalid credentials"
});
}
} catch (err) {
next(err);
}
*/
});
}
);

module.exports = router;
17 changes: 17 additions & 0 deletions api/auth/token-builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const jwt = require("jsonwebtoken")
const { JWT_SECRET } = require("./../secrets")

const buildToken = (user) => {
const payload = {
subject: user.user_id,
username: user.username,
role_name: user.role_name,
}
const options = {
expiresIn: "1d",
}
const signedToken = jwt.sign(payload, JWT_SECRET, options)
return signedToken;
}

module.exports = buildToken;
4 changes: 2 additions & 2 deletions api/secrets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
developers cloning this repo won't be able to run the project as is.
*/
module.exports = {

}
JWT_SECRET: process.env.JWT_SECRET || "shh"
};
53 changes: 39 additions & 14 deletions api/users/users-model.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const db = require('../../data/db-config.js');

function find() {
const find = async () => {
/**
You will need to join two tables.
Resolves to an ARRAY with all users.
Expand All @@ -18,9 +18,16 @@ function find() {
}
]
*/
const users = await db("users as u")
.leftJoin("roles as r",
"u.role_id", "r.role_id")
.select("u.user_id",
"u.username",
"r.role_name");
return users;
}

function findBy(filter) {
const findBy = async (filter) => {
/**
You will need to join two tables.
Resolves to an ARRAY with all users that match the filter condition.
Expand All @@ -34,9 +41,18 @@ function findBy(filter) {
}
]
*/
const filteredUsers = await db("users as u")
.leftJoin("roles as r",
"u.role_id", "r.role_id")
.select("u.user_id",
"u.username",
"u.password",
"r.role_name")
.where(filter);
return filteredUsers;
}

function findById(user_id) {
const findById = async (user_id) => {
/**
You will need to join two tables.
Resolves to the user with the given user_id.
Expand All @@ -47,6 +63,15 @@ function findById(user_id) {
"role_name": "instructor"
}
*/
const user = await db("users as u")
.leftJoin("roles as r",
"u.role_id", "r.role_id")
.select("u.user_id",
"u.username",
"r.role_name")
.where({ user_id })
.first();
return user;
}

/**
Expand All @@ -67,21 +92,21 @@ function findById(user_id) {
"role_name": "team lead"
}
*/
async function add({ username, password, role_name }) { // done for you
let created_user_id
const add = async ({ username, password, role_name }) => { // done for you
let created_user_id;
await db.transaction(async trx => {
let role_id_to_use
const [role] = await trx('roles').where('role_name', role_name)
let role_id_to_use;
const [role] = await trx('roles').where('role_name', role_name);
if (role) {
role_id_to_use = role.role_id
role_id_to_use = role.role_id;
} else {
const [role_id] = await trx('roles').insert({ role_name: role_name })
role_id_to_use = role_id
const [role_id] = await trx('roles').insert({ role_name: role_name });
role_id_to_use = role_id;
}
const [user_id] = await trx('users').insert({ username, password, role_id: role_id_to_use })
created_user_id = user_id
})
return findById(created_user_id)
const [user_id] = await trx('users').insert({ username, password, role_id: role_id_to_use });
created_user_id = user_id;
});
return findById(created_user_id);
}

module.exports = {
Expand Down
4 changes: 4 additions & 0 deletions config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
PORT: process.env.PORT || 9000,
BCRYPT_ROUNDS: process.env.BCRYPT_ROUNDS || 8
};
Binary file modified data/auth.db3
Binary file not shown.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const server = require('./api/server.js');
const { PORT } = require("./config");

const PORT = process.env.PORT || 9000;
const server = require('./api/server.js');

server.listen(PORT, () => {
console.log(`Listening on port ${PORT}...`);
Expand Down
Loading