Skip to content

Commit

Permalink
Section 8 complete
Browse files Browse the repository at this point in the history
  • Loading branch information
takuyadev committed Nov 4, 2022
1 parent da74a38 commit 2025183
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 6 deletions.
114 changes: 113 additions & 1 deletion controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const asyncHandler = require('../middleware/async')
const ErrorResponse = require('../utils/errorResponse')
const User = require('../model/User')
const sendEmail = require('../utils/sendEmail')
const crypto = require('crypto')

// @desc Register user
// @route POST /api/v1/auth/register
Expand Down Expand Up @@ -75,7 +77,6 @@ const sendTokenResponse = (user, statusCode, res) => {
})
}


// @desc Get current logged in user
// @route POST /api/v1/auth/me
// @access Private
Expand All @@ -86,4 +87,115 @@ exports.getMe = asyncHandler(async (req, res, next) => {
success: true,
data: user
})
})

// @desc Reset password
// @route POST /api/v1/resetpassword/:resettoken
// @access Public
exports.resetPassword = asyncHandler(async (req, res, next) => {

// Get hashed token
const resetPasswordToken = crypto
.createHash('sha256')
.update(req.params.resettoken)
.digest('hex')
console.log(resetPasswordToken)
const user = await User.findOne({
resetPasswordToken,
resetPasswordExpire: { $gt: Date.now() }
})

if (!user) {
return next(new ErrorResponse('Invalid token', 400))
}

// Set new password
user.password = req.body.password
user.resetPasswordToken = undefined
user.resetPasswordExpire = undefined

await user.save()

sendTokenResponse(user, 200, res)
})


// @desc Forgot password
// @route POST /api/v1/auth/forgotpassword
// @access Public
exports.forgotPassword = asyncHandler(async (req, res, next) => {
const user = await User.findOne({ email: req.body.email })

if (!user) {
return next(new ErrorResponse('There is no user with that email', 404))
}

// Get reset token
const resetToken = user.getResetPasswordToken()

await user.save({
validateBeforeSave: false
})

// Create reset url
const resetUrl = `${req.protocol}:https://${req.get('host')}/api/v1/auth/resetPassword/${resetToken}`

const message = `big message, here's the URL. ${resetUrl}`;

try {
await sendEmail({
email: user.email,
subject: 'Password reset token',
message
})
res.status(200).json({ success: true, data: 'Email sent' })
} catch (err) {
console.error(err)
user.resetPasswordToken = undefined
user.resetPasswordExpire = undefined
await user.save({ validateBeforeSave: false })
return next(new ErrorResponse('Error when resetting password', 401))
}

res.status(200).json({
success: true,
data: user
})
})

// @desc Update user details
// @route PUT /api/v1/auth/updatedetails
// @access Private
exports.updateDetails = asyncHandler(async (req, res, next) => {

const fieldsToUpdate = {
name: req.body.name,
email: req.body.email
}

const user = await User.findByIdAndUpdate(req.user.id, fieldsToUpdate, {
new: true,
runValidators: true
})

res.status(200).json({
success: true,
data: user
})
})

// @desc Update password
// @route PUT /api/v1/auth/updatepassword
// @access Private
exports.updatePassword = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.user.id).select('+password')

if (!(await user.matchPassword(req.body.currentPassword))) {
return next(new ErrorResponse('Password is incorrect', 401))
}

user.password = req.body.newPassword;
await user.save()

sendTokenResponse(user, 200, res)
})
28 changes: 26 additions & 2 deletions controllers/bootcamps.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ exports.getBootcamp = asyncHandler(async (req, res, next) => {
// @route POST /api/v1/bootcamps
// @access Private
exports.createBootcamp = asyncHandler(async (req, res, next) => {
// Add user to req.body
req.body.user = req.user.id;

// Check for published bootcamp
const publishedBootcamp = await Bootcamp.findOne({ user: req.user.id })

console.log(publishedBootcamp)
// If the user is not an admin, they can only add one bootcamp
if (publishedBootcamp && req.user.role !== 'admin') {
return next(
new ErrorResponse(`The user with ID ${req.user.id} has already published a bootcamp`, 404))
}


const bootcamp = await Bootcamp.create(req.body)

if (!bootcamp) {
Expand All @@ -45,15 +59,25 @@ exports.createBootcamp = asyncHandler(async (req, res, next) => {
// @route PUT /api/v1/bootcamps
// @access Private
exports.updateBootcamp = asyncHandler(async (req, res, next) => {
const bootcamp = await Bootcamp.findByIdAndUpdate(req.params.id, req.body, {
let bootcamp = await Bootcamp.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
})

if (!bootcamp) {
res.status(400).json({ success: false })
return next(next(new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404)))
}

// Make sure user is bootcamp owner
if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(next(new ErrorResponse(`User ${req.params.id} is not authorized to update this bootcamp`, 404)))
}

bootcamp = await Bootcamp.findOneAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
})

res
.status(200)
.json({ success: true, data: bootcamp })
Expand Down
18 changes: 17 additions & 1 deletion controllers/courses.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ exports.getCourse = asyncHandler(async (req, res, next) => {
// @access Private
exports.addCourse = asyncHandler(async (req, res, next) => {
req.body.bootcamp = req.params.bootcampId;
req.body.user = req.user.id

const bootcamp = await Bootcamp.findById(req.params.bootcampId)

if (!bootcamp) {
return next(new ErrorResponse(`No bootcamp with the id of ${req.params.bootcampId}`), 404)
}

// Make sure user is bootcamp owner
if (bootcamp.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(next(new ErrorResponse(`User ${req.params.id} is not authorized to add a course to ${bootcamp._id}`, 404)))
}

const course = await Courses.create(req.body)

res.status(200).json({
Expand All @@ -81,6 +87,11 @@ exports.updateCourse = asyncHandler(async (req, res, next) => {
return next(new ErrorResponse(`No course with the id of ${req.params.id}`), 404)
}

// Make sure user is course owner
if (course.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(next(new ErrorResponse(`User ${req.params.id} is not authorized to update course ${bootcamp._id}`, 404)))
}

course = await Courses.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
Expand All @@ -102,10 +113,15 @@ exports.deleteCourse = asyncHandler(async (req, res, next) => {
return next(new ErrorResponse(`No course with the id of ${req.params.id}`), 404)
}

// Make sure user is course owner
if (course.user.toString() !== req.user.id && req.user.role !== 'admin') {
return next(next(new ErrorResponse(`User ${req.params.id} is not authorized to delete course ${bootcamp._id}`, 404)))
}

await course.remove()

res.status(200).json({
succes: true,
success: true,
data: {}
})
})
Expand Down
61 changes: 61 additions & 0 deletions controllers/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');
const User = require('../model/User');

// @desc Get all users
// @route GET /api/v1/users
// @access Private/Admin
exports.getUsers = asyncHandler(async (req, res, next) => {
res.status(200).json(res.advancedResults);
});

// @desc Get single user
// @route GET /api/v1/users/:id
// @access Private/Admin
exports.getUser = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.params.id);

res.status(200).json({
success: true,
data: user
});
});

// @desc Create user
// @route POST /api/v1/users
// @access Private/Admin
exports.createUser = asyncHandler(async (req, res, next) => {
const user = await User.create(req.body);

res.status(201).json({
success: true,
data: user
});
});

// @desc Update user
// @route PUT /api/v1/users/:id
// @access Private/Admin
exports.updateUser = asyncHandler(async (req, res, next) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});

res.status(200).json({
success: true,
data: user
});
});

// @desc Delete user
// @route DELETE /api/v1/users/:id
// @access Private/Admin
exports.deleteUser = asyncHandler(async (req, res, next) => {
await User.findByIdAndDelete(req.params.id);

res.status(200).json({
success: true,
data: {}
});
});
6 changes: 6 additions & 0 deletions model/Bootcamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ const BootcampSchema = new mongoose.Schema({
createdAt: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Schema.ObjectId,
// Reference to Schema, not database
ref: 'User',
required: true
}
}, {
toJSON: { virtuals: true },
Expand Down
6 changes: 6 additions & 0 deletions model/Courses.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ const CourseSchema = new mongoose.Schema({
// Reference to Schema, not database
ref: 'Bootcamp',
required: true
},
user: {
type: mongoose.Schema.ObjectId,
// Reference to Schema, not database
ref: 'User',
required: true
}
})

Expand Down
19 changes: 18 additions & 1 deletion model/User.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const crypto = require('crypto')
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')

const UserSchema = new mongoose.Schema({
name: {
type: String,
Expand Down Expand Up @@ -35,7 +37,10 @@ const UserSchema = new mongoose.Schema({
})

// Encrypt password using bcrypt
UserSchema.pre('save', async function () {
UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
next()
}
const salt = await bcrypt.genSalt(10)
this.password = await bcrypt.hash(this.password, salt)
})
Expand All @@ -52,5 +57,17 @@ UserSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}

UserSchema.methods.getResetPasswordToken = function () {
// Generate token
const resetToken = crypto.randomBytes(20).toString('hex')

// Hash token and set to resetPasswordToken field
this.resetPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex')

// Set expire
this.resetPasswordExpire = Date.now() + 10 * 60 * 1000

return resetToken
}

module.exports = mongoose.model('User', UserSchema)
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2025183

Please sign in to comment.