Commit 4b9e320f authored by Egor Kremnev's avatar Egor Kremnev

add auth user

parent 6025b9c0
...@@ -14,9 +14,9 @@ const UserSchema = new Schema({ ...@@ -14,9 +14,9 @@ const UserSchema = new Schema({
required: true, required: true,
unique: true, unique: true,
validate: { validate: {
validator: async (username) => { validator: async function (username) {
const user = await User.findOne({username}); const user = await User.findOne({username});
return !user; return !user || user._id !== this._id;
}, },
message: "This user is already exists" message: "This user is already exists"
} }
......
...@@ -5,6 +5,7 @@ const multer = require('multer'); ...@@ -5,6 +5,7 @@ const multer = require('multer');
const path = require('path'); const path = require('path');
const {uploadPath} = require('./../config'); const {uploadPath} = require('./../config');
const Product = require('../models/Product'); const Product = require('../models/Product');
const User = require("../models/User");
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: (req, file, cb) => { destination: (req, file, cb) => {
...@@ -19,6 +20,18 @@ const upload = multer({storage}); ...@@ -19,6 +20,18 @@ const upload = multer({storage});
const createRoutes = () => { const createRoutes = () => {
router.post('/', upload.single('image'), async (req, res) => { router.post('/', upload.single('image'), async (req, res) => {
const token = req.get('Authorization');
if (!token) return res
.status(401)
.send({error: 'No token present'});
const user = await User.findOne({token});
if (!user) return res
.status(401)
.send('Token is wrong');
const productData = {...req.body}; const productData = {...req.body};
if (req.file) productData.image = req.file.filename; if (req.file) productData.image = req.file.filename;
......
...@@ -25,10 +25,16 @@ router.post('/login', async (req, res) => { ...@@ -25,10 +25,16 @@ router.post('/login', async (req, res) => {
.status(400) .status(400)
.send({error: 'Username or password incorrect'}); .send({error: 'Username or password incorrect'});
try {
user.generateToken(); user.generateToken();
await user.save(); await user.save();
res.send({message: "User success authenticated", user}); res.send(user);
} catch (e) {
res
.status(400)
.send({error: e.message});
}
}); });
router.get('/profile', async (req, res) => { router.get('/profile', async (req, res) => {
......
import {Route, Routes} from "react-router-dom"; import {Route, Routes} from "react-router-dom";
import {PRODUCT_ADD, PRODUCT_LIST, PRODUCT_VIEW, REGISTER} from "./constants/routes"; import {LOGIN, PRODUCT_ADD, PRODUCT_LIST, PRODUCT_VIEW, REGISTER} from "./constants/routes";
import Layout from "./components/Layout/Layout"; import Layout from "./components/Layout/Layout";
import Products from "./containers/Products/Products"; import Products from "./containers/Products/Products";
import AddProduct from "./containers/Addproduct/AddProduct"; import AddProduct from "./containers/Addproduct/AddProduct";
import Register from "./containers/Auth/Register/Register"; import Register from "./containers/Auth/Register/Register";
import Login from "./containers/Auth/Login/Login";
import {useSelector} from "react-redux";
import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute";
import axiosApi from "./api/axiosApi";
const App = () => ( const App = () => {
<> const user = useSelector(({usersState}) => usersState.user);
<Routes>
const productAdd = <ProtectedRoute
isAllowed={!!user}
redirectPath={LOGIN}
>
<AddProduct />
</ProtectedRoute>;
return <Routes>
<Route element={<Layout />}> <Route element={<Layout />}>
<Route index element={<Products />}/> <Route index element={<Products />}/>
<Route path={REGISTER} element={<Register />}/> <Route path={REGISTER} element={<Register />}/>
<Route path={LOGIN} element={<Login />}/>
<Route path={PRODUCT_LIST} element={<Products />}/> <Route path={PRODUCT_LIST} element={<Products />}/>
<Route path={PRODUCT_ADD} element={<AddProduct />}/> <Route path={PRODUCT_ADD} element={productAdd}/>
<Route path={PRODUCT_VIEW} element={<h1>Show product</h1>}/> <Route path={PRODUCT_VIEW} element={<h1>Show product</h1>}/>
</Route> </Route>
</Routes> </Routes>;
</> };
);
export default App; export default App;
import {useState} from "react"; import {useState} from "react";
import {Button, Grid, TextField} from "@mui/material"; import {Button, Grid, TextField} from "@mui/material";
import FileInput from "../../UI/FileInput/FileInput"; import FileInput from "../../UI/Form/FormElement/FileInput/FileInput";
const ProductForm = ({createProductHandler}) => { const ProductForm = ({createProductHandler}) => {
const [state, setState] = useState({ const [state, setState] = useState({
......
import {Navigate, Outlet} from "react-router-dom";
const ProtectedRoute = ({isAllowed, redirectPath, children}) => {
if (!isAllowed) {
return <Navigate to={redirectPath} replace />;
}
return children || <Outlet/>;
};
export default ProtectedRoute;
import {AppBar, Box, Toolbar, Typography, Button, IconButton} from '@mui/material'; import {AppBar, Box, Toolbar, Typography, Button, IconButton, Menu, MenuItem} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu'; import HomeIcon from '@mui/icons-material/Home';
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import {MAIN, REGISTER} from "../../../constants/routes"; import {MAIN, REGISTER, LOGIN} from "../../../constants/routes";
import {useSelector} from "react-redux";
import {useState} from 'react';
const AppToolbar = () => { const AppToolbar = () => {
const user = useSelector(({usersState}) => usersState.user);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = e => {
setAnchorEl(e.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return ( return (
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<AppBar position="static"> <AppBar position="static">
...@@ -14,14 +28,41 @@ const AppToolbar = () => { ...@@ -14,14 +28,41 @@ const AppToolbar = () => {
color="inherit" color="inherit"
aria-label="menu" aria-label="menu"
sx={{ mr: 2 }} sx={{ mr: 2 }}
component={NavLink}
to={MAIN}
> >
<MenuIcon /> <HomeIcon />
</IconButton> </IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Shop Shop
</Typography> </Typography>
<Button color="inherit" component={NavLink} to={MAIN}>Home</Button> {
user
? <>
<Button
aria-controls="simple-menu"
aria-haspopup={true}
onClick={handleClick}
color="inherit"
>
Hello, {user.username}
</Button>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={handleClose}
keepMounted
>
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem>Logout</MenuItem>
</Menu>
</>
: <>
<Button color="inherit" component={NavLink} to={REGISTER}>Sign up</Button> <Button color="inherit" component={NavLink} to={REGISTER}>Sign up</Button>
<Button color="inherit" component={NavLink} to={LOGIN}>Sign In</Button>
</>
}
</Toolbar> </Toolbar>
</AppBar> </AppBar>
</Box> </Box>
......
import {Grid, TextField} from "@mui/material";
import PropTypes from "prop-types";
const TextInput = ({name, label, value, onChange, required, error, type}) => {
return <Grid item xs={12}>
<TextField
fullWidth
required={required}
id={name}
name={name}
label={label}
error={!!error}
helperText={error}
value={value}
onChange={onChange}
autoComplete={name}
type={type}
/>
</Grid>;
};
TextInput.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
type: PropTypes.string,
error: PropTypes.string,
required: PropTypes.bool,
onChange: PropTypes.func.isRequired
};
export default TextInput;
import {useState} from "react";
import {Avatar, Button, Container, Grid, Typography, Link, Box, CssBaseline, Alert} from "@mui/material";
import { createTheme, ThemeProvider } from '@mui/material/styles';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {Link as RouterLink, useNavigate} from 'react-router-dom';
import {REGISTER} from "../../../constants/routes";
import {useDispatch, useSelector} from "react-redux";
import {loginUser} from "../../../store/actions/usersActions";
import TextInput from "../../../components/UI/Form/FormElement/TextInput/TextInput";
const theme = createTheme();
const Login = () => {
const error = useSelector(({usersState}) => usersState.loginError);
const dispatch = useDispatch();
const navigate = useNavigate();
const [state, setState] = useState({
username: "",
password: ""
});
const inputChangeHandler = (e) => {
const {name, value} = e.currentTarget;
setState(prevState => {
return {...prevState, [name]: value};
});
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginUser({
data: {...state},
callback: () => navigate('/')
}));
};
return (
<ThemeProvider theme={theme}>
<Container component="main" maxWidth="xs">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign In
</Typography>
{error && <Alert severity="error">{error.error}</Alert>}
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<TextInput
required={true}
label="Username"
name="username"
onChange={inputChangeHandler}
value={state.username}
/>
<TextInput
required={true}
name="password"
label="Password"
type="password"
onChange={inputChangeHandler}
value={state.password}
/>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
<Grid container justifyContent="flex-end">
<Grid item>
<Link href="#" variant="body2" component={RouterLink} to={REGISTER}>
Sign Up
</Link>
</Grid>
</Grid>
</Box>
</Box>
</Container>
</ThemeProvider>
);
};
export default Login;
import {useState} from "react"; import {useState} from "react";
import {Avatar, Button, Container, Grid, TextField, Typography, Link, Box, CssBaseline} from "@mui/material"; import {Avatar, Button, Container, Grid, Typography, Link, Box, CssBaseline} from "@mui/material";
import { createTheme, ThemeProvider } from '@mui/material/styles'; import { createTheme, ThemeProvider } from '@mui/material/styles';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {Link as RouterLink, useNavigate} from 'react-router-dom'; import {Link as RouterLink, useNavigate} from 'react-router-dom';
import {LOGIN} from "../../../constants/routes"; import {LOGIN} from "../../../constants/routes";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {registerUser} from "../../../store/actions/usersActions"; import {registerUser} from "../../../store/actions/usersActions";
import TextInput from "../../../components/UI/Form/FormElement/TextInput/TextInput";
const theme = createTheme(); const theme = createTheme();
const Register = () => { const Register = () => {
const error = useSelector(({usersState}) => usersState.error); const error = useSelector(({usersState}) => usersState.registerError);
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [state, setState] = useState({ const [state, setState] = useState({
...@@ -58,36 +59,24 @@ const Register = () => { ...@@ -58,36 +59,24 @@ const Register = () => {
</Typography> </Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}> <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <TextInput
<TextField required={true}
required
fullWidth
id="username"
label="Username" label="Username"
name="username" name="username"
autoComplete="family-name"
onChange={inputChangeHandler} onChange={inputChangeHandler}
value={state.username} value={state.username}
error={!!getFieldError('username')} error={getFieldError('username')}
helperText={getFieldError('username')}
/> />
</Grid> <TextInput
<Grid item xs={12}> required={true}
<TextField
required
fullWidth
name="password" name="password"
label="Password" label="Password"
type="password" type="password"
id="password"
autoComplete="new-password"
onChange={inputChangeHandler} onChange={inputChangeHandler}
value={state.password} value={state.password}
error={!!getFieldError('password')} error={getFieldError('password')}
helperText={getFieldError('password')}
/> />
</Grid> </Grid>
</Grid>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
......
...@@ -8,5 +8,7 @@ export const fetchProducts = createAsyncThunk( ...@@ -8,5 +8,7 @@ export const fetchProducts = createAsyncThunk(
export const createProduct = createAsyncThunk( export const createProduct = createAsyncThunk(
'products/create', 'products/create',
async (data) => await axios.post('/products', data).then(res => res.data) async (data) => await axios
.post('/products', data)
.then(res => res.data)
); );
import {createAsyncThunk} from "@reduxjs/toolkit"; import {createAsyncThunk} from "@reduxjs/toolkit";
import axiosApi from "../../api/axiosApi"; import axiosApi from "../../api/axiosApi";
import {setError} from "../services/usersSlice"; import {setLoginError, setRegisterError, setUser} from "../services/usersSlice";
export const registerUser = createAsyncThunk( export const registerUser = createAsyncThunk(
'users/register', 'users/register',
...@@ -8,7 +8,23 @@ export const registerUser = createAsyncThunk( ...@@ -8,7 +8,23 @@ export const registerUser = createAsyncThunk(
.post('/users', payload.data) .post('/users', payload.data)
.then(res => payload.callback()) .then(res => payload.callback())
.catch(e => { .catch(e => {
if (e?.response?.data) dispatch(setError(e.response.data)); if (e?.response?.data) dispatch(setRegisterError(e.response.data));
else dispatch(setRegisterError(e));
throw e;
})
);
export const loginUser = createAsyncThunk(
'users/login',
async (payload, {dispatch, getState}) => await axiosApi
.post('/users/login', payload.data)
.then(res => {
dispatch(setUser(res.data));
payload.callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data));
else dispatch(setLoginError(e));
throw e; throw e;
}) })
); );
import {createSlice} from "@reduxjs/toolkit"; import {createSlice} from "@reduxjs/toolkit";
import {registerUser} from "../actions/usersActions"; import {loginUser, registerUser} from "../actions/usersActions";
const initialState = { const initialState = {
error: null, loginError: null,
loading: false registerError: null,
loading: false,
user: null
}; };
const usersSlice = createSlice({ const usersSlice = createSlice({
name: 'users', name: 'users',
initialState, initialState,
reducers: { reducers: {
setError: (state, action) => { setLoginError: (state, action) => {
state.error = action.payload; state.loginError = action.payload;
},
setRegisterError: (state, action) => {
state.registerError = action.payload;
},
setUser: (state, action) => {
state.user = action.payload;
} }
}, },
extraReducers: builder => { extraReducers: builder => {
...@@ -19,8 +27,9 @@ const usersSlice = createSlice({ ...@@ -19,8 +27,9 @@ const usersSlice = createSlice({
.addCase( .addCase(
registerUser.pending, registerUser.pending,
state => { state => {
state.error = null; state.registerError = null;
state.loading = true; state.loading = true;
state.user = null;
} }
) )
.addCase( .addCase(
...@@ -35,8 +44,30 @@ const usersSlice = createSlice({ ...@@ -35,8 +44,30 @@ const usersSlice = createSlice({
state.loading = false; state.loading = false;
} }
); );
builder
.addCase(
loginUser.pending,
state => {
state.loginError = null;
state.loading = true;
state.user = null;
}
)
.addCase(
loginUser.rejected,
state => {
state.loading = false;
}
)
.addCase(
loginUser.fulfilled,
state => {
state.loading = false;
}
);
} }
}); });
export const {setError} = usersSlice.actions; export const {setLoginError, setRegisterError, setUser} = usersSlice.actions;
export default usersSlice.reducer; export default usersSlice.reducer;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment