├── client
├── src
│ ├── index.css
│ ├── components
│ │ ├── PrivateRoute.jsx
│ │ ├── Header.jsx
│ │ └── OAuth.jsx
│ ├── main.jsx
│ ├── firebase.js
│ ├── redux
│ │ ├── store.js
│ │ └── user
│ │ │ └── userSlice.js
│ ├── App.jsx
│ └── pages
│ │ ├── About.jsx
│ │ ├── Home.jsx
│ │ ├── SignIn.jsx
│ │ ├── SignUp.jsx
│ │ └── Profile.jsx
├── postcss.config.js
├── tailwind.config.js
├── index.html
├── vite.config.js
├── README.md
├── .eslintrc.cjs
└── package.json
├── api
├── utils
│ ├── error.js
│ └── verifyUser.js
├── routes
│ ├── auth.route.js
│ └── user.route.js
├── models
│ └── user.model.js
├── index.js
└── controllers
│ ├── user.controller.js
│ └── auth.controller.js
├── .gitignore
└── package.json
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/api/utils/error.js:
--------------------------------------------------------------------------------
1 | export const errorHandler = (statusCode, message) => {
2 | const error = new Error();
3 | error.statusCode = statusCode;
4 | error.message = message;
5 | return error;
6 | };
7 |
--------------------------------------------------------------------------------
/client/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
--------------------------------------------------------------------------------
/client/src/components/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import {useSelector} from 'react-redux'
2 | import { Outlet, Navigate } from 'react-router-dom'
3 |
4 | export default function PrivateRoute() {
5 | const {currentUser} = useSelector(state => state.user)
6 | return currentUser ? :
7 | }
8 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Auth App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/api/routes/auth.route.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import { signin, signup, google, signout } from '../controllers/auth.controller.js';
3 |
4 | const router = express.Router();
5 |
6 | router.post('/signup', signup);
7 | router.post('/signin', signin);
8 | router.post('/google', google);
9 | router.get('/signout', signout);
10 |
11 | export default router;
12 |
--------------------------------------------------------------------------------
/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react-swc';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | server: {
7 | proxy: {
8 | '/api': {
9 | target: 'http://localhost:3000',
10 | secure: false,
11 | },
12 | },
13 | },
14 | plugins: [react()],
15 | });
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .env
26 |
--------------------------------------------------------------------------------
/api/routes/user.route.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import {
3 | test,
4 | updateUser,
5 | deleteUser,
6 | } from '../controllers/user.controller.js';
7 | import { verifyToken } from '../utils/verifyUser.js';
8 |
9 | const router = express.Router();
10 |
11 | router.get('/', test);
12 | router.post('/update/:id', verifyToken, updateUser);
13 | router.delete('/delete/:id', verifyToken, deleteUser);
14 |
15 | export default router;
16 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/api/utils/verifyUser.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken';
2 | import { errorHandler } from './error.js';
3 |
4 | export const verifyToken = (req, res, next) => {
5 | const token = req.cookies.access_token;
6 |
7 | if (!token) return next(errorHandler(401, 'You are not authenticated!'));
8 |
9 | jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
10 | if (err) return next(errorHandler(403, 'Token is not valid!'));
11 |
12 | req.user = user;
13 | next();
14 | });
15 |
16 |
17 | }
--------------------------------------------------------------------------------
/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.jsx';
4 | import './index.css';
5 | import { persistor, store } from './redux/store.js';
6 | import { Provider } from 'react-redux';
7 | import { PersistGate } from 'redux-persist/integration/react';
8 |
9 | ReactDOM.createRoot(document.getElementById('root')).render(
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mern-auth",
3 | "version": "1.0.0",
4 | "description": "",
5 | "type": "module",
6 | "main": "index.js",
7 | "scripts": {
8 | "dev": "nodemon api/index.js",
9 | "start": "node api/index.js",
10 | "build": "npm install && npm install --prefix client && npm run build --prefix client"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "bcryptjs": "^2.4.3",
17 | "cookie-parser": "^1.4.6",
18 | "dotenv": "^16.3.1",
19 | "express": "^4.18.2",
20 | "jsonwebtoken": "^9.0.1",
21 | "mongoose": "^7.4.2",
22 | "nodemon": "^3.0.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from 'firebase/app';
3 | // TODO: Add SDKs for Firebase products that you want to use
4 | // https://firebase.google.com/docs/web/setup#available-libraries
5 |
6 | // Your web app's Firebase configuration
7 | const firebaseConfig = {
8 | apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
9 | authDomain: 'mern-auth-1c4ae.firebaseapp.com',
10 | projectId: 'mern-auth-1c4ae',
11 | storageBucket: 'mern-auth-1c4ae.appspot.com',
12 | messagingSenderId: '277641423672',
13 | appId: '1:277641423672:web:2de25252aae022d51aafcd',
14 | };
15 |
16 | // Initialize Firebase
17 | export const app = initializeApp(firebaseConfig);
18 |
--------------------------------------------------------------------------------
/api/models/user.model.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | username: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | },
10 | email: {
11 | type: String,
12 | required: true,
13 | unique: true,
14 | },
15 | password: {
16 | type: String,
17 | required: true,
18 | },
19 | profilePicture: {
20 | type: String,
21 | default:
22 | 'https://img.freepik.com/premium-vector/man-avatar-profile-picture-vector-illustration_268834-538.jpg',
23 | },
24 | },
25 | { timestamps: true }
26 | );
27 |
28 | const User = mongoose.model('User', userSchema);
29 |
30 | export default User;
31 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, configureStore } from '@reduxjs/toolkit';
2 | import userReducer from './user/userSlice.js';
3 | import { persistReducer, persistStore } from 'redux-persist';
4 | import storage from 'redux-persist/lib/storage';
5 |
6 | const rootReducer = combineReducers({ user: userReducer });
7 |
8 | const persistConfig = {
9 | key: 'root',
10 | version: 1,
11 | storage,
12 | };
13 |
14 | const persistedReducer = persistReducer(persistConfig, rootReducer);
15 |
16 | export const store = configureStore({
17 | reducer: persistedReducer,
18 | middleware: (getDefaultMiddleware) =>
19 | getDefaultMiddleware({
20 | serializableCheck: false,
21 | }),
22 | });
23 |
24 | export const persistor = persistStore(store);
25 |
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from 'react-router-dom';
2 | import Home from './pages/Home';
3 | import About from './pages/About';
4 | import SignIn from './pages/SignIn';
5 | import SignUp from './pages/SignUp';
6 | import Profile from './pages/Profile';
7 | import Header from './components/Header';
8 | import PrivateRoute from './components/PrivateRoute';
9 |
10 | export default function App() {
11 | return (
12 |
13 | {/* header */}
14 |
15 |
16 | } />
17 | } />
18 | } />
19 | } />
20 | }>
21 | } />
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import { useSelector } from 'react-redux';
3 |
4 | export default function Header() {
5 | const { currentUser } = useSelector((state) => state.user);
6 | return (
7 |
8 |
9 |
10 |
Auth App
11 |
12 |
13 |
14 | Home
15 |
16 |
17 | About
18 |
19 |
20 | {currentUser ? (
21 |
22 | ) : (
23 | Sign In
24 | )}
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.5",
14 | "firebase": "^10.1.0",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.2",
18 | "react-router-dom": "^6.14.2",
19 | "redux-persist": "^6.0.0"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^18.2.15",
23 | "@types/react-dom": "^18.2.7",
24 | "@vitejs/plugin-react-swc": "^3.3.2",
25 | "autoprefixer": "^10.4.14",
26 | "eslint": "^8.45.0",
27 | "eslint-plugin-react": "^7.32.2",
28 | "eslint-plugin-react-hooks": "^4.6.0",
29 | "eslint-plugin-react-refresh": "^0.4.3",
30 | "postcss": "^8.4.27",
31 | "tailwindcss": "^3.3.3",
32 | "vite": "^4.4.5"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/pages/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function About() {
4 | return (
5 |
6 |
About
7 |
8 | This is a MERN (MongoDB, Express, React, Node.js) stack application with
9 | authentication. It allows users to sign up, log in, and log out, and
10 | provides access to protected routes only for authenticated users.
11 |
12 |
13 | The front-end of the application is built with React and uses React
14 | Router for client-side routing. The back-end is built with Node.js and
15 | Express, and uses MongoDB as the database. Authentication is implemented
16 | using JSON Web Tokens (JWT).
17 |
18 |
19 | This application is intended as a starting point for building full-stack
20 | web applications with authentication using the MERN stack. Feel free to
21 | use it as a template for your own projects!
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/api/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import mongoose from 'mongoose';
3 | import dotenv from 'dotenv';
4 | import userRoutes from './routes/user.route.js';
5 | import authRoutes from './routes/auth.route.js';
6 | import cookieParser from 'cookie-parser';
7 | import path from 'path';
8 | dotenv.config();
9 |
10 | mongoose
11 | .connect(process.env.MONGO)
12 | .then(() => {
13 | console.log('Connected to MongoDB');
14 | })
15 | .catch((err) => {
16 | console.log(err);
17 | });
18 |
19 | const __dirname = path.resolve();
20 |
21 | const app = express();
22 |
23 | app.use(express.static(path.join(__dirname, '/client/dist')));
24 |
25 | app.get('*', (req, res) => {
26 | res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'));
27 | });
28 |
29 | app.use(express.json());
30 |
31 | app.use(cookieParser());
32 |
33 | app.listen(3000, () => {
34 | console.log('Server listening on port 3000');
35 | });
36 |
37 | app.use('/api/user', userRoutes);
38 | app.use('/api/auth', authRoutes);
39 |
40 | app.use((err, req, res, next) => {
41 | const statusCode = err.statusCode || 500;
42 | const message = err.message || 'Internal Server Error';
43 | return res.status(statusCode).json({
44 | success: false,
45 | message,
46 | statusCode,
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/client/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 | Welcome to my Auth App!
8 |
9 |
10 | This is a full-stack web application built with the MERN (MongoDB,
11 | Express, React, Node.js) stack. It includes authentication features that
12 | allow users to sign up, log in, and log out, and provides access to
13 | protected routes only for authenticated users.
14 |
15 |
16 | The front-end of the application is built with React and uses React
17 | Router for client-side routing. The back-end is built with Node.js and
18 | Express, and uses MongoDB as the database. Authentication is implemented
19 | using JSON Web Tokens (JWT).
20 |
21 |
22 | This application is intended as a starting point for building full-stack
23 | web applications with authentication using the MERN stack. Feel free to
24 | use it as a template for your own projects!
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/client/src/components/OAuth.jsx:
--------------------------------------------------------------------------------
1 | import { GoogleAuthProvider, signInWithPopup, getAuth } from 'firebase/auth';
2 | import { app } from '../firebase';
3 | import { useDispatch } from 'react-redux';
4 | import { signInSuccess } from '../redux/user/userSlice';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | export default function OAuth() {
8 | const dispatch = useDispatch();
9 | const navigate = useNavigate();
10 | const handleGoogleClick = async () => {
11 | try {
12 | const provider = new GoogleAuthProvider();
13 | const auth = getAuth(app);
14 |
15 | const result = await signInWithPopup(auth, provider);
16 | const res = await fetch('/api/auth/google', {
17 | method: 'POST',
18 | headers: {
19 | 'Content-Type': 'application/json',
20 | },
21 | body: JSON.stringify({
22 | name: result.user.displayName,
23 | email: result.user.email,
24 | photo: result.user.photoURL,
25 | }),
26 | });
27 | const data = await res.json();
28 | console.log(data);
29 | dispatch(signInSuccess(data));
30 | navigate('/');
31 | } catch (error) {
32 | console.log('could not login with google', error);
33 | }
34 | };
35 | return (
36 |
41 | Continue with google
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/api/controllers/user.controller.js:
--------------------------------------------------------------------------------
1 | import User from '../models/user.model.js';
2 | import { errorHandler } from '../utils/error.js';
3 | import bcryptjs from 'bcryptjs';
4 |
5 | export const test = (req, res) => {
6 | res.json({
7 | message: 'API is working!',
8 | });
9 | };
10 |
11 | // update user
12 |
13 | export const updateUser = async (req, res, next) => {
14 | if (req.user.id !== req.params.id) {
15 | return next(errorHandler(401, 'You can update only your account!'));
16 | }
17 | try {
18 | if (req.body.password) {
19 | req.body.password = bcryptjs.hashSync(req.body.password, 10);
20 | }
21 |
22 | const updatedUser = await User.findByIdAndUpdate(
23 | req.params.id,
24 | {
25 | $set: {
26 | username: req.body.username,
27 | email: req.body.email,
28 | password: req.body.password,
29 | profilePicture: req.body.profilePicture,
30 | },
31 | },
32 | { new: true }
33 | );
34 | const { password, ...rest } = updatedUser._doc;
35 | res.status(200).json(rest);
36 | } catch (error) {
37 | next(error);
38 | }
39 | };
40 |
41 |
42 | // delete user
43 |
44 |
45 | export const deleteUser = async (req, res, next) => {
46 | if (req.user.id !== req.params.id) {
47 | return next(errorHandler(401, 'You can delete only your account!'));
48 | }
49 | try {
50 | await User.findByIdAndDelete(req.params.id);
51 | res.status(200).json('User has been deleted...');
52 | } catch (error) {
53 | next(error);
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/client/src/redux/user/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const initialState = {
4 | currentUser: null,
5 | loading: false,
6 | error: false,
7 | };
8 |
9 | const userSlice = createSlice({
10 | name: 'user',
11 | initialState,
12 | reducers: {
13 | signInStart: (state) => {
14 | state.loading = true;
15 | },
16 | signInSuccess: (state, action) => {
17 | state.currentUser = action.payload;
18 | state.loading = false;
19 | state.error = false;
20 | },
21 | signInFailure: (state, action) => {
22 | state.loading = false;
23 | state.error = action.payload;
24 | },
25 | updateUserStart: (state) => {
26 | state.loading = true;
27 | },
28 | updateUserSuccess: (state, action) => {
29 | state.currentUser = action.payload;
30 | state.loading = false;
31 | state.error = false;
32 | },
33 | updateUserFailure: (state, action) => {
34 | state.loading = false;
35 | state.error = action.payload;
36 | },
37 | deleteUserStart: (state) => {
38 | state.loading = true;
39 | },
40 | deleteUserSuccess: (state) => {
41 | state.currentUser = null;
42 | state.loading = false;
43 | state.error = false;
44 | },
45 | deleteUserFailure: (state, action) => {
46 | state.loading = false;
47 | state.error = action.payload;
48 | },
49 | signOut: (state) => {
50 | state.currentUser = null;
51 | state.loading = false;
52 | state.error = false;
53 | },
54 | },
55 | });
56 |
57 | export const {
58 | signInStart,
59 | signInSuccess,
60 | signInFailure,
61 | updateUserFailure,
62 | updateUserStart,
63 | updateUserSuccess,
64 | deleteUserFailure,
65 | deleteUserStart,
66 | deleteUserSuccess,
67 | signOut,
68 | } = userSlice.actions;
69 |
70 | export default userSlice.reducer;
71 |
--------------------------------------------------------------------------------
/client/src/pages/SignIn.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import {
4 | signInStart,
5 | signInSuccess,
6 | signInFailure,
7 | } from '../redux/user/userSlice';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import OAuth from '../components/OAuth';
10 |
11 | export default function SignIn() {
12 | const [formData, setFormData] = useState({});
13 | const { loading, error } = useSelector((state) => state.user);
14 |
15 | const navigate = useNavigate();
16 | const dispatch = useDispatch();
17 | const handleChange = (e) => {
18 | setFormData({ ...formData, [e.target.id]: e.target.value });
19 | };
20 |
21 | const handleSubmit = async (e) => {
22 | e.preventDefault();
23 | try {
24 | dispatch(signInStart());
25 | const res = await fetch('/api/auth/signin', {
26 | method: 'POST',
27 | headers: {
28 | 'Content-Type': 'application/json',
29 | },
30 | body: JSON.stringify(formData),
31 | });
32 | const data = await res.json();
33 | if (data.success === false) {
34 | dispatch(signInFailure(data));
35 | return;
36 | }
37 | dispatch(signInSuccess(data));
38 | navigate('/');
39 | } catch (error) {
40 | dispatch(signInFailure(error));
41 | }
42 | };
43 | return (
44 |
45 |
Sign In
46 |
69 |
70 |
Dont Have an account?
71 |
72 |
Sign up
73 |
74 |
75 |
76 | {error ? error.message || 'Something went wrong!' : ''}
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/client/src/pages/SignUp.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import OAuth from '../components/OAuth';
4 |
5 | export default function SignUp() {
6 | const [formData, setFormData] = useState({});
7 | const [error, setError] = useState(false);
8 | const [loading, setLoading] = useState(false);
9 | const navigate = useNavigate();
10 | const handleChange = (e) => {
11 | setFormData({ ...formData, [e.target.id]: e.target.value });
12 | };
13 |
14 | const handleSubmit = async (e) => {
15 | e.preventDefault();
16 | try {
17 | setLoading(true);
18 | setError(false);
19 | const res = await fetch('/api/auth/signup', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | body: JSON.stringify(formData),
25 | });
26 | const data = await res.json();
27 | console.log(data);
28 | setLoading(false);
29 | if (data.success === false) {
30 | setError(true);
31 | return;
32 | }
33 | navigate('/sign-in');
34 | } catch (error) {
35 | setLoading(false);
36 | setError(true);
37 | }
38 | };
39 | return (
40 |
41 |
Sign Up
42 |
72 |
73 |
Have an account?
74 |
75 |
Sign in
76 |
77 |
78 |
{error && 'Something went wrong!'}
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/api/controllers/auth.controller.js:
--------------------------------------------------------------------------------
1 | import User from '../models/user.model.js';
2 | import bcryptjs from 'bcryptjs';
3 | import { errorHandler } from '../utils/error.js';
4 | import jwt from 'jsonwebtoken';
5 |
6 | export const signup = async (req, res, next) => {
7 | const { username, email, password } = req.body;
8 | const hashedPassword = bcryptjs.hashSync(password, 10);
9 | const newUser = new User({ username, email, password: hashedPassword });
10 | try {
11 | await newUser.save();
12 | res.status(201).json({ message: 'User created successfully' });
13 | } catch (error) {
14 | next(error);
15 | }
16 | };
17 |
18 | export const signin = async (req, res, next) => {
19 | const { email, password } = req.body;
20 | try {
21 | const validUser = await User.findOne({ email });
22 | if (!validUser) return next(errorHandler(404, 'User not found'));
23 | const validPassword = bcryptjs.compareSync(password, validUser.password);
24 | if (!validPassword) return next(errorHandler(401, 'wrong credentials'));
25 | const token = jwt.sign({ id: validUser._id }, process.env.JWT_SECRET);
26 | const { password: hashedPassword, ...rest } = validUser._doc;
27 | const expiryDate = new Date(Date.now() + 3600000); // 1 hour
28 | res
29 | .cookie('access_token', token, { httpOnly: true, expires: expiryDate })
30 | .status(200)
31 | .json(rest);
32 | } catch (error) {
33 | next(error);
34 | }
35 | };
36 |
37 | export const google = async (req, res, next) => {
38 | try {
39 | const user = await User.findOne({ email: req.body.email });
40 | if (user) {
41 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
42 | const { password: hashedPassword, ...rest } = user._doc;
43 | const expiryDate = new Date(Date.now() + 3600000); // 1 hour
44 | res
45 | .cookie('access_token', token, {
46 | httpOnly: true,
47 | expires: expiryDate,
48 | })
49 | .status(200)
50 | .json(rest);
51 | } else {
52 | const generatedPassword =
53 | Math.random().toString(36).slice(-8) +
54 | Math.random().toString(36).slice(-8);
55 | const hashedPassword = bcryptjs.hashSync(generatedPassword, 10);
56 | const newUser = new User({
57 | username:
58 | req.body.name.split(' ').join('').toLowerCase() +
59 | Math.random().toString(36).slice(-8),
60 | email: req.body.email,
61 | password: hashedPassword,
62 | profilePicture: req.body.photo,
63 | });
64 | await newUser.save();
65 | const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET);
66 | const { password: hashedPassword2, ...rest } = newUser._doc;
67 | const expiryDate = new Date(Date.now() + 3600000); // 1 hour
68 | res
69 | .cookie('access_token', token, {
70 | httpOnly: true,
71 | expires: expiryDate,
72 | })
73 | .status(200)
74 | .json(rest);
75 | }
76 | } catch (error) {
77 | next(error);
78 | }
79 | };
80 |
81 | export const signout = (req, res) => {
82 | res.clearCookie('access_token').status(200).json('Signout success!');
83 | };
84 |
--------------------------------------------------------------------------------
/client/src/pages/Profile.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from 'react-redux';
2 | import { useRef, useState, useEffect } from 'react';
3 | import {
4 | getDownloadURL,
5 | getStorage,
6 | ref,
7 | uploadBytesResumable,
8 | } from 'firebase/storage';
9 | import { app } from '../firebase';
10 | import { useDispatch } from 'react-redux';
11 | import {
12 | updateUserStart,
13 | updateUserSuccess,
14 | updateUserFailure,
15 | deleteUserStart,
16 | deleteUserSuccess,
17 | deleteUserFailure,
18 | signOut,
19 | } from '../redux/user/userSlice';
20 |
21 | export default function Profile() {
22 | const dispatch = useDispatch();
23 | const fileRef = useRef(null);
24 | const [image, setImage] = useState(undefined);
25 | const [imagePercent, setImagePercent] = useState(0);
26 | const [imageError, setImageError] = useState(false);
27 | const [formData, setFormData] = useState({});
28 | const [updateSuccess, setUpdateSuccess] = useState(false);
29 |
30 | const { currentUser, loading, error } = useSelector((state) => state.user);
31 | useEffect(() => {
32 | if (image) {
33 | handleFileUpload(image);
34 | }
35 | }, [image]);
36 | const handleFileUpload = async (image) => {
37 | const storage = getStorage(app);
38 | const fileName = new Date().getTime() + image.name;
39 | const storageRef = ref(storage, fileName);
40 | const uploadTask = uploadBytesResumable(storageRef, image);
41 | uploadTask.on(
42 | 'state_changed',
43 | (snapshot) => {
44 | const progress =
45 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
46 | setImagePercent(Math.round(progress));
47 | },
48 | (error) => {
49 | setImageError(true);
50 | },
51 | () => {
52 | getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) =>
53 | setFormData({ ...formData, profilePicture: downloadURL })
54 | );
55 | }
56 | );
57 | };
58 | const handleChange = (e) => {
59 | setFormData({ ...formData, [e.target.id]: e.target.value });
60 | };
61 |
62 | const handleSubmit = async (e) => {
63 | e.preventDefault();
64 | try {
65 | dispatch(updateUserStart());
66 | const res = await fetch(`/api/user/update/${currentUser._id}`, {
67 | method: 'POST',
68 | headers: {
69 | 'Content-Type': 'application/json',
70 | },
71 | body: JSON.stringify(formData),
72 | });
73 | const data = await res.json();
74 | if (data.success === false) {
75 | dispatch(updateUserFailure(data));
76 | return;
77 | }
78 | dispatch(updateUserSuccess(data));
79 | setUpdateSuccess(true);
80 | } catch (error) {
81 | dispatch(updateUserFailure(error));
82 | }
83 | };
84 |
85 | const handleDeleteAccount = async () => {
86 | try {
87 | dispatch(deleteUserStart());
88 | const res = await fetch(`/api/user/delete/${currentUser._id}`, {
89 | method: 'DELETE',
90 | });
91 | const data = await res.json();
92 | if (data.success === false) {
93 | dispatch(deleteUserFailure(data));
94 | return;
95 | }
96 | dispatch(deleteUserSuccess(data));
97 | } catch (error) {
98 | dispatch(deleteUserFailure(error));
99 | }
100 | };
101 |
102 | const handleSignOut = async () => {
103 | try {
104 | await fetch('/api/auth/signout');
105 | dispatch(signOut())
106 | } catch (error) {
107 | console.log(error);
108 | }
109 | };
110 | return (
111 |
112 |
Profile
113 |
173 |
174 |
178 | Delete Account
179 |
180 |
181 | Sign out
182 |
183 |
184 |
{error && 'Something went wrong!'}
185 |
186 | {updateSuccess && 'User is updated successfully!'}
187 |
188 |
189 | );
190 | }
191 |
--------------------------------------------------------------------------------