diff --git a/client/src/components/ErrorMessage.jsx b/client/src/components/ErrorMessage.jsx index 44210aa..38ab7f4 100644 --- a/client/src/components/ErrorMessage.jsx +++ b/client/src/components/ErrorMessage.jsx @@ -1,12 +1,16 @@ import React from "react"; -import { Alert } from "react-bootstrap"; -const ErrorMessage = ({ variant = "info", children }) => { +const ErrorMessage = ({ errors, variant }) => { + if (!errors) { + return null; + } return ( - - {children} - +
+ {Object.values(errors).map((error) => ( +

{error}

+ ))} +
); }; -export default ErrorMessage; \ No newline at end of file +export default ErrorMessage; diff --git a/client/src/hooks/useAuth.js b/client/src/hooks/useAuth.js new file mode 100644 index 0000000..47d8146 --- /dev/null +++ b/client/src/hooks/useAuth.js @@ -0,0 +1,97 @@ +import { useEffect, useReducer } from "react"; +import axios from "axios"; + +const useAuth = () => { + const [state, dispatch] = useReducer( + (state, action) => { + switch (action.type) { + case "reset": + return { + loading: false, + error: null, + user: null, + }; + case "error": + return { + ...state, + loading: false, + error: action.error, + }; + case "login": + case "register": + return { + ...state, + loading: true, + }; + case "success": + return { + loading: false, + error: null, + user: action.user, + }; + default: + return state; + } + }, + { + loading: false, + error: null, + user: null, + } + ); + + const register = async (values) => { + dispatch({ type: "register" }); + try { + const response = await axios.post("/api/users/register", values, { + baseURL: "http://localhost:5000", + headers: { + "Content-Type": "application/json", + }, + }); + const { data } = response; + localStorage.setItem("userInfo", JSON.stringify(data)); + dispatch({ type: "success", user: data }); + } catch (error) { + dispatch({ type: "error", error: error.message }); + } + }; + + const login = async (values) => { + dispatch({ type: "login" }); + try { + const response = await axios.post("/api/users/login", values, { + baseURL: "http://localhost:5000", + headers: { + "Content-Type": "application/json", + }, + }); + const { data } = response; + localStorage.setItem("userInfo", JSON.stringify(data)); + dispatch({ type: "success", user: data }); + } catch (error) { + dispatch({ type: "error", error: error.message }); + } + }; + + const logout = () => { + localStorage.removeItem("userInfo"); + dispatch({ type: "reset" }); + }; + + useEffect(() => { + const user = JSON.parse(localStorage.getItem("userInfo")); + if (user) { + dispatch({ type: "success", user }); + } + }, []); + + return { + ...state, + register, + login, + logout, + }; +}; + +export default useAuth; diff --git a/client/src/hooks/useMakeRequest.js b/client/src/hooks/useMakeRequest.js new file mode 100644 index 0000000..4ba0627 --- /dev/null +++ b/client/src/hooks/useMakeRequest.js @@ -0,0 +1,32 @@ +import { useState } from "react"; +import axios from "axios"; + +const useMakeRequest = ({ + requestMethod, + requestUrl, + requestData, + requestConfig, +}) => { + const [response, setResponse] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const makeRequest = async () => { + setLoading(true); + try { + const res = await axios[requestMethod]( + requestUrl, + requestData, + requestConfig + ); + setResponse(res); + } catch (err) { + setError(err); + } + setLoading(false); + }; + + return { makeRequest, response, error, loading }; +}; + +export default useMakeRequest; diff --git a/client/src/screens/LoginPage/LoginPage.jsx b/client/src/screens/LoginPage/LoginPage.jsx index 9a5b990..4fab485 100644 --- a/client/src/screens/LoginPage/LoginPage.jsx +++ b/client/src/screens/LoginPage/LoginPage.jsx @@ -16,7 +16,7 @@ const LoginPage = () => { }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - + const submitHandler = async (event) => { event.preventDefault(); setLoading(true); @@ -29,11 +29,12 @@ const LoginPage = () => { }); localStorage.setItem("userInfo", JSON.stringify(response.data)); - console.log(values); + console.log(response.data); setLoading(false); resetForm(); } catch (err) { - setError(err.message); + console.log(err); + setError(err.error); setLoading(false); } }; @@ -42,7 +43,7 @@ const LoginPage = () => {
{loading && } - {error &&
{error}
} + {error &&
{error}
}
Email address diff --git a/client/src/screens/RegisterPage/RegisterPage.jsx b/client/src/screens/RegisterPage/RegisterPage.jsx index 1133b6c..dfe6bdb 100644 --- a/client/src/screens/RegisterPage/RegisterPage.jsx +++ b/client/src/screens/RegisterPage/RegisterPage.jsx @@ -1,94 +1,104 @@ -import React,{useState,useEffect} from 'react' -import './register.css' -import MainScreen from './../../components/MainScreen'; -import { Form ,Button} from 'react-bootstrap'; -import axios from 'axios' -import ErrorMessage from './../../components/ErrorMessage'; -import Loading from './../../components/Loading'; +import React, { useState } from "react"; +import "./register.css"; +import MainScreen from "./../../components/MainScreen"; +import { Form, Button } from "react-bootstrap"; +import ErrorMessage from "./../../components/ErrorMessage"; +import Loading from "./../../components/Loading"; +import useForm from "../../hooks/useForm"; +import axios from "axios"; + const RegisterPage = () => { - const [email, setEmail] = useState(""); - const [name, setName] = useState(""); - const [pic, setPic] = useState( - "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg" - ); - const [password, setPassword] = useState(""); - const [confirmpassword, setConfirmPassword] = useState(""); - const [message, setMessage] = useState(null); - const [picMessage, setPicMessage] = useState(null); - const[error,setError] =useState(false) - const[loading,setLoading] =useState(false) - const submitHandler = async(e)=>{ - e.preventDefault() - if(password !== confirmpassword){ - setMessage("passwords do not match") - }else{ - setMessage(null) - try{ - setLoading(true) - const {data}= await axios.post("/api/users/",{name,pic,email,password}, { - baseURL: "http://localhost:5000", - headers: { - "Content-Type": "application/json", - } - }); - setLoading(false) + const [values, handleChange, resetForm] = useForm({ + email: "", + name: "", + pic: "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg", + password: "", + confirmpassword: "", + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const submitHandler = async (event) => { + event.preventDefault(); + setLoading(true); + try { + const response = await axios.post("/api/users/register", values, { + baseURL: "http://localhost:5000", + headers: { + "Content-Type": "application/json", + }, + }); - localStorage.setItem("userInfo", JSON.stringify(data)) - } - catch(err){ - setError(error.response.data.message) - } - } - console.log(email); + localStorage.setItem("userInfo", JSON.stringify(response.data)); + console.log(values); + setLoading(false); + resetForm(); + } catch (err) { + setError(err.message); + setLoading(false); } + }; + return ( -
- {error && {error}} - {message && {message}} - {loading && } +
+ + {loading && } - - Enter name - setName(e.target.value)} /> - + + Enter name + + - - Email address - setEmail(e.target.value)} /> - + + Email address + + - - Password - setPassword(e.target.value)}/> - - - confirm Password - setConfirmPassword(e.target.value)} /> - - - upload profile picture - - + + Password + + + + confirm Password + + + + upload profile picture + + - - -
+ + +
- ) -} + ); +}; -export default RegisterPage \ No newline at end of file +export default RegisterPage; diff --git a/server/controllers/userControllers.js b/server/controllers/userControllers.js index a72d0ea..f47fb4f 100644 --- a/server/controllers/userControllers.js +++ b/server/controllers/userControllers.js @@ -1,51 +1,69 @@ -const User = require('../models/userModel.js') -const ascyncHandler = require('express-async-handler') -const generateToken = require('../utill/generateToken') - -const registerUser = ascyncHandler(async(req,res)=>{ - const { name, email ,password,pic } = req.body; - const userExist = await User.findOne({email}); - if(userExist){ - res.status(400) - throw new Error("User already exists") - } - const user = await User.create({ - name, - email, - password, - pic +const User = require("../models/userModel.js"); +const asyncHandler = require("express-async-handler"); +const generateToken = require("../utill/generateToken"); +const encryptionUtil = require("../utill/encryptionUtil"); +const bcrypt = require("bcrypt"); + +const registerUser = asyncHandler(async (req, res) => { + const { name, email, password, pic } = req.body; + const userExist = await User.findOne({ email }); + if (userExist) { + return res.status(409).json({ message: "User already exists" }); + } + + const { salt, encryptedPassword } = await encryptionUtil.encryptPassword( + password + ); + + const user = new User({ + name, + email, + password: encryptedPassword, + pic, + salt, + }); + await user.save(); + + if (user) { + res.status(201).json({ + _id: user._id, + name: user.name, + isAdmin: user.isAdmin, + pic: user.pic, + token: generateToken(user._id), + }); + } else { + res.status(500).send({ error: "Error occured" }); + } +}); + +const authUser = asyncHandler(async (req, res) => { + const { email, password } = req.body; + const user = await User.findOne({ email }, "_id name isAdmin pic").select( + "+password" + ); + + if (!user) { + return res.status(404).json({ error: "User not found" }); + } + + const isValidPassword = await encryptionUtil.comparePassword( + password, + user.salt, + user.password + ); + + if (!isValidPassword) { + res.json({ + _id: user._id, + name: user.name, + isAdmin: user.isAdmin, + pic: user.pic, + token: generateToken(user._id), }); + } else { + res.status(401).send({ error: "Incorrect password" }); + } +}); - if(user){ - res.status(201).json({ - _id:user._id, - name: user.name, - isAdmin: user.isAdmin, - pic:user.pic, - token: generateToken(user._id) - }) - } - else{ - res.status(400) - throw new Error ("Error occured") - } -}) - -const authUser = ascyncHandler(async(req,res)=>{ - const { email ,password} = req.body; - const user = await User.findOne({email}) - if(user && (await user.matchPassword(password))){ - res.json({ - _id:user._id, - name: user.name, - isAdmin: user.isAdmin, - pic:user.pic, - token: generateToken(user._id) - }) - } - else{ - res.status(400) - throw new Error ("Invalid Email or password") - } -}) -module.exports ={ registerUser,authUser} \ No newline at end of file +module.exports = { registerUser, authUser }; diff --git a/server/index.js b/server/index.js index 1a95be8..5c9d4d0 100644 --- a/server/index.js +++ b/server/index.js @@ -3,8 +3,7 @@ require("dotenv").config(); const notes = require("./data/noes.js"); const userRoutes = require("./routes/userRoutes"); const connectDB = require("./config/db"); -const { notFound, errorHandler } = require("./middlewares/errorMiddleWare"); -const cors= require('cors') +const cors = require("cors"); const PORT = process.env.PORT || 5000; const app = express(); @@ -13,16 +12,17 @@ connectDB(); // app.use(notFound , errorHandler) app.use(express.json()); -app.use(cors({ - allowedHeaders: "*", - allowedMethods:"*", - origin:"*" -})) +app.use( + cors({ + allowedHeaders: "*", + allowedMethods: "*", + origin: "*", + }) +); app.get("/", (req, res) => { res.send("Hello"); }); - app.get("/api/notes", (req, res) => { res.json(notes); }); @@ -35,4 +35,4 @@ app.use("/api/users", userRoutes); app.listen(PORT, () => { console.log(`App is running on http://localhost:${PORT}`); -}); \ No newline at end of file +}); diff --git a/server/middlewares/errorMiddleWare.js b/server/middlewares/errorMiddleWare.js index 48fe8fb..c0f1f28 100644 --- a/server/middlewares/errorMiddleWare.js +++ b/server/middlewares/errorMiddleWare.js @@ -1,16 +1,16 @@ const notFound = (req, res, next) => { - const error = new Error(`Not Found - ${req.originalUrl}`); - res.status(404); - next(error); - }; - - const errorHandler = (err, req, res, next) => { - const statusCode = res.statusCode === 200 ? 500 : res.statusCode; - res.status(statusCode); - res.json({ - message: err.message, - stack: process.env.NODE_ENV === "production" ? null : err.stack, - }); - }; - - module.exports = { notFound, errorHandler }; \ No newline at end of file + const error = new Error(`Not Found - ${req.originalUrl}`); + res.status(404); + next(error); +}; + +const errorHandler = (err, req, res, next) => { + const statusCode = res.statusCode === 200 ? 500 : res.statusCode; + res.status(statusCode); + res.json({ + message: err.message, + stack: process.env.NODE_ENV === "production" ? null : err.stack, + }); +}; + +module.exports = { notFound, errorHandler }; diff --git a/server/models/userModel.js b/server/models/userModel.js index 7c9686b..f9ce949 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -1,47 +1,37 @@ -const mongoose = require('mongoose') -const bcrypt = require('bcrypt') -const userSchema = mongoose.Schema({ - - name:{ - type:String, - required:true - }, - email:{ - type:String, - required:true, - unique:true - }, - password:{ - type:String, - required:true - }, - isAdmin:{ - type:Boolean, - required:true, - default:false - }, - pic:{ - type:String, - required:true, - default: "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg" - }, - -},{ - timestamps:true, -}) +const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); +const userSchema = mongoose.Schema( + { + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + isAdmin: { + type: Boolean, + required: true, + default: false, + }, + pic: { + type: String, + required: true, + default: + "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg", + }, + }, + { + timestamps: true, + } +); -userSchema.pre('save', async function(next){ - if(!this.isModified('password')){ - next(); - } +const User = mongoose.model("User", userSchema); - const salt = await bcrypt.genSalt(10) - this.password= await bcrypt.hash(this.password,salt); -}) -userSchema.methods.matchPassword = async function (enteredPassword) { - return await bcrypt.compare(enteredPassword, this.password); - }; - -const User = mongoose.model("User", userSchema) - -module.exports = User \ No newline at end of file +module.exports = User; diff --git a/server/routes/userRoutes.js b/server/routes/userRoutes.js index ac59ad9..8a09e34 100644 --- a/server/routes/userRoutes.js +++ b/server/routes/userRoutes.js @@ -2,7 +2,7 @@ const express = require("express") const {registerUser,authUser} = require('../controllers/userControllers') const router = express.Router() -router.route('/').post(registerUser) +router.route('/register').post(registerUser) router.route('/login').post(authUser) diff --git a/server/utill/encryptionUtil.js b/server/utill/encryptionUtil.js new file mode 100644 index 0000000..2de3de0 --- /dev/null +++ b/server/utill/encryptionUtil.js @@ -0,0 +1,19 @@ +const bcrypt = require("bcrypt"); + +const encryptionUtil = { + async encryptPassword(password) { + const salt = await bcrypt.genSalt(); + const encryptedPassword = await bcrypt.hash(password, salt); + return { salt, encryptedPassword }; + }, + + async comparePassword(password, salt, encryptedPasswordToCompareTo) { + const { encryptedPassword } = await encryptionUtil.encryptPassword( + password, + salt + ); + return encryptedPassword === encryptedPasswordToCompareTo; + }, +}; + +module.exports = encryptionUtil;