This is a rest API that fetches movie data, similar to Netflix. I've created a login route for new users and registered users, however, when I test the API on postman and attempt to log in as a user I get an error message:
Errors:
Expect Output:
I want the user to log in successfully
The Problem:
I can't find anything wrong with the code despite reference errors
Question:
What kind of data and hash arguments do I require? What is causing the problem?
Code:
model.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
let movieSchema = mongoose.Schema({
Title: {
type: String,
required: true
},
Description: {
type: String,
required: true
},
Genre: {
Name: String,
Description: String
},
Director: {
Name: String,
Bio: String,
Birth: String
},
Actors: [String],
ImageURL: String,
Featured: Boolean
});
let userSchema = mongoose.Schema({
Username: {
type: String,
required: true
},
Password: {
type: String,
required: true
},
Email: {
type: String,
required: true
},
Birthday: Date,
FavoriteMovies: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Movie'
}]
});
// HASH THE PASSWORD BEFORE SAVING THE USER MODEL TO THE DATABASE
userSchema.statics.hashPassword = (password) => {
return bcrypt.hashSync(password, 10);
};
// COMPARE THE HASHED PASSWORD IN THE DATABASE WITH THE ONE PROVIDED BY THE USER WHEN THEY LOG IN
userSchema.methods.validatePassword = function(password) {
return bcrypt.compareSync(password, this.Password);
};
// SCHEMA FOR DIRECTORS
let directorSchema = mongoose.Schema({
Name: {
type: String,
required: true
},
Bio: {
type: String,
required: true
},
Birth: {
type: String,
required: true
}
})
/// SCHEMA FOR GENRES
let genreSchema = mongoose.Schema({
Name: {
type: String,
required: true
},
Description: {
type: String,
required: true
}
})
// CREATE MODELS FROM SCHEMAS
let Movie = mongoose.model('Movie', movieSchema);
let User = mongoose.model('User', userSchema);
let Director = mongoose.model('Director', directorSchema);
let Genre = mongoose.model('Genre', genreSchema);
// EXPORT MODELS
module.exports.Movie = Movie;
module.exports.User = User;
module.exports.Director = Director;
module.exports.Genre = Genre;
passport.js
const passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
Models = require('./models/models'),
passportJWT = require('passport-jwt');
let Users = Models.User,
JWTStrategy = passportJWT.Strategy,
ExtractJWT = passportJWT.ExtractJwt;
passport.use(new LocalStrategy({
usernameField: 'Username',
passwordField: 'Password'
}, (username, password, callback) => {
console.log(`${username} ${password}`);
Users.findOne({
Username: username
}, (error, user) => {
if (error) {
console.log(error);
return callback(error);
}
if (!user) {
console.log('incorrect username');
return callback(null, false, {
message: 'Incorrect username or password.'
});
}
if (!user.validatePassword(password)) {
console.log('incorrect password');
return callback(null, false, {
message: 'Incorrect password.'
});
}
console.log('finished');
return callback(null, user);
});
}));
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: 'your_jwt_secret'
}, async(jwtPayload, callback) => {
try {
const user = await Users.findById(jwtPayload._id);
return callback(null, user);
} catch (error) {
return callback(error);
}
}));
index.js
const express = require('express'),
morgan = require('morgan'),
bodyParser = require('body-parser'),
uuid = require('uuid'),
mongoose = require('mongoose'),
Models = require('./models/models'),
{
check,
validationResult
} = require('express-validator');
const app = express();
// SCHEMAS
const Movies = Models.Movie;
const Users = Models.User;
const Genres = Models.Genre;
const Directors = Models.Director;
mongoose.connect('mongodb://localhost:27017/myMovies', {
useNewUrlParser: true,
useUnifiedTopology: true
}); // DATABASE Option 1: Local DB
// mongoose.connect(process.env.CONNECTION_URI, { useNewUrlParser: true, dbName: "myMoviesDB", useUnifiedTopology: true }); // REMOTE DATABASE Option 2: Remote DB
// MIDDLEWARE
app.use(bodyParser.json());
// LOGGING MIDDLEWARE
const cors = require('cors');
// ALL ORIGINS ARE ALLOWED TO ACCESS THE API ENDPOINTS
app.use(cors());
// CETAIN ORIGINS ARE ALLOWED TO ACCESS THE API ENDPOINTS:
// let allowedOrigins = ['http://localhost:8080', 'http://testsite.com']
// app.use(cors({
// origin: (origin, callback) => {
// if (!origin) return callback(null, true); // i don't understand this line
// if (allowedOrigins.indexOf(origin) === -1){ // specific origin not in allowedOrigins list
// let message = 'The CORS policy for this application doesn’t allow access from origin ' + origin;
// return callback(new Error(message), false);
// }
// return callback(null, true);
// }
// }));
// ERROR HANDLING MIDDLEWARE
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// INPUT VALIDATION
const userValidation = [
check('Username', 'Username is required').isLength({
min: 5
}),
check('Username', 'Username contains non alphanumeric characters - not allowed.').isAlphanumeric(),
check('Password', 'Password is required').not().isEmpty(),
check('Email', 'Email does not appear to be valid').isEmail()
];
// INPUT VALIDATION FOR MOVIES
const movieValidation = [
check('Title', 'Title is required').not().isEmpty(),
check('Description', 'Description is required').not().isEmpty(),
check('Genre', 'Genre is required').not().isEmpty(),
check('Director', 'Director is required').not().isEmpty(),
check('Actors', 'Actors is required').not().isEmpty(),
check('ImageURL', 'ImageURL is required').not().isEmpty(),
check('Featured', 'Featured is required').not().isEmpty()
];
//AUTHENTICATION
require('./auth')(app);
const passport = require('passport');
const {
Passport
} = require('passport');
require('./passport');
// LOGS REQUESTS TO THE CONSOLE
app.use(morgan('common'));
app.use(express.static('public'));
//ROUTING / HOME
app.get("/", (req, res) => {
res.send('Hello there! Welcome to myMovies');
})
// LIST OF ALL MOVIES
app.get('/users', passport.authenticate('jwt', {
session: false
}), (req, res) => {
Users.find()
.then((allUsers) => {
res.status(201).json(allUsers);
})
.catch((err) => {
console.error(err);
res.status(500).send(`Error: ${err}`);
});
});
// ADD User
app.post('/users', userValidation, (req, res) => {
//check for validation errors
let errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({
errors: errors.array()
});
}
let hashedPassword = Users.hashPassword(req.body.Password);
Users.findOne({
Username: req.body.Username
})
.then((user) => {
if (user) {
return res.status(400).send(`Username ${req.body.Username} already taken.`);
} else {
Users
.create({
Username: req.body.Username,
Password: hashedPassword,
Email: req.body.Email,
Birthday: req.body.Birthday
})
.then((new_user) => {
res.status(201).json(new_user)
})
.catch((error) => {
console.error(error);
res.status(500).send(`Error: ${error}`);
})
}
})
.catch((error) => {
console.error(error);
res.status(500).send(`Error: ${error}`);
});
});
// REMOVE USER
app.delete('/users/:Username', passport.authenticate('jwt', {
session: false
}), (req, res) => {
// ONLY ALLOWS USERS TO DELETE THEIR OWN ACCOUNT
if (req.user.Username !== req.params.Username) {
res.status(403).json('You are not authorized to delete this user.');
} else {
Users.findOneAndRemove({
Username: req.params.Username
})
.then((user) => {
if (!user) {
res.status(400).send(`${req.params.Username} was not found!`);
} else {
res.status(200).send(`${req.params.Username} was deleted`);
}
}).catch((err) => {
console.error(err);
res.status(500).send(`Error: ${err}`);
});
}
});
// GET USER INFO BY USERNAME
app.get('/users/:Username', passport.authenticate('jwt', {
session: false
}), (req, res) => {
Users.findOne({
Username: req.params.Username
}).then((user) => {
res.json(user);
}).catch((err) => {
console.error(err);
res.status(500).send(`Error: ${err}`);
});
});
// UPDATE USER INFO
app.put('/users/:Username', passport.authenticate('jwt', {
session: false
}), userValidation, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({
errors: errors.array()
});
}
Users.findOneAndUpdate({
Username: req.params.Username
}, {
$set: {
Username: req.body.Username,
Password: req.body.Password,
Email: req.body.Email,
Birthday: req.body.Birthday
}
}, {
new: true
}, // This line ensures that the updated document is returned
(err, updateUser) => {
if (err) {
console.error(err);
res.status(500).send(`Error: ${err}`);
} else {
res.json(updateUser); // Return the updated document
}
})
});
// MOVIES
// GET: fetches a list of all movies
// app.get('/movies', (req, res) => {
// Movies.find()
// .then((movies) => {
// res.status(201).json(movies);
// })
// .catch((error) => {
// console.error(error);
// res.status(500).send(`Error: ${error}`);
// });
// });
app.get('/movies', passport.authenticate('jwt', {
session: false
}), (req, res) => {
Movies.find()
.then((movies) => {
res.status(201).json(movies);
})
.catch((error) => {
console.error(error);
res.status(500).send(`Error: ${error}`);
});
});
// GET: fetches movies by title
app.get('/movies/:Title', passport.authenticate('jwt', {
sesson: false
}), (req, res) => {
Movies.findOne({
Title: req.params.Title
})
.then((movie) => {
res.json(movie);
}).catch((err) => {
console.error(err);
res.status(500).send('Error: ' + err);
});
});
// ADD MOVIE TO FAVORITES LIST OF USER
app.post('/users/:Username/movies/:MovieID',
passport.authenticate({
session: false
}),
(req, res) => {
if (req.user.Username !== req.params.Username) {
res.status(403).json('Not allowed to add movie to another user\'s favorites list');
} else {
Users.findOneAndUpdate({
Username: req.params.Username
}, {
$addToSet: {
FavoriteMovies: req.params.MovieID
}
}, {
$push: {
FavoriteMovies: req.params.MovieID
}
}, {
new: true
}, // This line ensures that the updated document is returned
(err, updated_User) => {
if (err) {
console.error(err);
res.status(500).send(`Error:${err}`)
} else {
res.json(updated_User)
}
});
}
});
// REMOVE MOVIE FROM FAVORITES
app.delete('/users/:Username/movies/:MovieID', passport.authenticate('jwt', {
session: false
}), (req, res) => {
// ONLY ALLOQWS USERS TO REMOVE MOVIES FROM THEIR OWN FAVORITES
if (req.user.Username != req.params.Username) {
res.status(403).json('Not allowed to remove movie from another user\'s favorites list');
} else {
Users.findOneAndUpdate({
Username: req.params.Username
}, {
pull: {
FavoriteMovies: req.params.MovieID
}
}, {
new: true
}) // THIS LINE ENSURES THAT THE UPDATED DOCUMENT IS RETURNED
.then((updatedUser) => {
res.status(200).send(`Favorite movie removed from ${updatedUser.Username}`);
})
.catch((err) => {
console.error(err);
res.status(500).send(`Error: ${err}`);
});
}
});
// DIRECTORS
// RETURN A LIST OF ALL DIRECTORS BY NAME (BIO, BIRTHYEAR)
app.get('/directors/:Name', passport.authenticate('jwt', {
session: false
}), (req, res) => {
Directors.findOne({
Name: req.params.Name
}).then((director) => {
res.json(director);
}).catch((err) => {
console.error(err);
res.status(500).send('Error: ' + err); // 500: INTERNAL SERVER ERROR
})
})
// GENRES
// GET: returns all genres
app.get('/genres', (req, res) => {
Genres.find().then((genre) => {
res.status(201).json(genre);
}).catch((err) => {
console.error(err);
res.status(400).send('Error: ' + err);
})
})
// RETURNS GENRE BY NAME
app.get('/genres/:Name', (req, res) => {
Genres.findOne({
Name: req.params.Name
}).then((genreName) => {
res.status(201).json(genreName)
}).catch((err) => {
console.error(err);
res.status(500).send('Error: ' + err);
})
})
// DOCUMENTATION ROUTE
app.get('/documentation', (req, res) => {
res.sendFile('public/documentation.html');
});
// SERVER & HEROKU
const port = process.env.PORT || 8081;
app.listen(port, '0.0.0.0', () => {
console.log(`Listening on Port ${port}`);
});
Research:
I've searched and read through most of the common questions related to the issue, but none helped.
Thank you in advance for taking the time to answer my question.