0

So I am following a react/redux/mongodb fullstack tutorial.

Right now, I am working on implementing tokens for user authentication and route authentication, and have no idea how it is working.

The tutorial appears to be taking the User model from the models folder, from the server-side. If I were to deploy this site would that even work? I thought any communication between the front-end and back-end could only be done via an api call.

Other than the server-side user model, I cannot discern where this 'user' is coming from.

Am I missing something?

I have no idea how 'user' is in the state. (<-This is what I am trying to figure out)

Side thoughts/questions:

-Is the tutorial doing this because having a user model in the front-end (that can display all of the data types (user_id, username, password, email, etc)) can be a security risk?

-Are models even necessary? I have been told you need them for user management in the back-end. (To allow the server to discern from one user to another). --But do you still need a user model for the front end? Can't you just verify the token on every link/call?

Here are the files:

This is the user's dashboard (after they have logged in). I do not understand where the user in const UserDashboard = ({user}) => comes from.

User/index.js


import React from 'react';
import UserLayout from '../../hoc/user';
import MyButton from '../utils/button';
import UserHistoryBlock from '../utils/User/history_block';

const UserDashboard = ({user}) => {
    return (
        <UserLayout>
            <div>

                <div className="user_nfo_panel">
                    <h1>User information</h1>
                    <div>
                        <span>{user.userData.name}</span>
                        <span>{user.userData.lastname}</span>
                        <span>{user.userData.email}</span>
                    </div>
                    <MyButton
                        type="default"
                        title="Edit account info"
                        linkTo="/user/user_profile"
                    />
                </div>

                {
                    user.userData.history ?
                    <div className="user_nfo_panel">
                        <h1>History purchases</h1>
                        <div className="user_product_block_wrapper">
                            <UserHistoryBlock
                                products={user.userData.history}
                            />
                        </div>            
                    </div>

                    :null
                }


            </div>
        </UserLayout>

    );
};

export default UserDashboard;

This is the user dashboard's layout. These are the changes in the nav bar buttons after a user has logged in. I do not understand where user: state.user in mapStateToProps() has come from.

hoc/user.js


import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';

const links = [
    {
        name: 'My account',
        linkTo: '/user/dashboard'
    },
    {
        name: 'User information',
        linkTo: '/user/user_profile'
    },
    {
        name: 'My Cart',
        linkTo: '/user/cart'
    },
]

const admin = [
    {
        name: 'Site info',
        linkTo: '/admin/site_info'
    },
    {
        name: 'Add products',
        linkTo: '/admin/add_product'
    },
    {
        name: 'Manage categories',
        linkTo: '/admin/manage_categories'
    },
    {
        name: 'Upload file',
        linkTo: '/admin/add_file'
    }
]


const UserLayout = (props) => {

    const generateLinks = (links) => (
        links.map((item,i)=>(
            <Link to={item.linkTo} key={i}>
                {item.name}
            </Link>
        ))
    )


    return (
        <div className="container">
            <div className="user_container">
                <div className="user_left_nav">
                    <h2>My account</h2>
                    <div className="links">
                        { generateLinks(links)}
                    </div>
                    { props.user.userData.isAdmin ?
                        <div>
                            <h2>Admin</h2>
                            <div className="links">
                                { generateLinks(admin)}
                            </div>
                        </div>
                    :null
                    }

                </div>
                <div className="user_right">
                    {props.children}
                </div>
            </div>
        </div>
    );
};

const mapStateToProps = (state) => {
    return {
        user: state.user
    }
}


export default connect(mapStateToProps)(UserLayout);

Here is the Routes file that uses the user dashboard as a route with it's inputs. (Its the first route)

    routes.js


    import React from 'react';
    import { Switch, Route } from 'react-router-dom';

    import Layout from './hoc/layout';
    import Auth from './hoc/auth';

    import Home from './components/Home';
    import RegisterLogin from './components/Register_login';
    import Register from './components/Register_login/register';
    import Shop from './components/Shop';
    import ProductPage from './components/Product';
    import ResetUser from './components/Reset_user';
    import ResetPass from './components/Reset_user/reset_pass';

    import UserDashboard from './components/User';
    import AddProduct from './components/User/Admin/add_product';
    import ManageCategories from './components/User/Admin/manage_categories';
    import UserCart from './components/User/cart';
    import UpdateProfile from './components/User/update_profile';
    import ManageSite from './components/User/Admin/manage_site';
    import AddFile from './components/User/Admin/add_file';

    import PageNotFound from './components/utils/page_not_found';


    const Routes = () => {
      return(
        <Layout>
          <Switch>
            <Route path="/user/dashboard" exact component= 
               {Auth(UserDashboard,true)}/>
            <Route path="/user/cart" exact component= 
               {Auth(UserCart,true)}/>
            <Route path="/user/user_profile" exact component=            
               {Auth(UpdateProfile,true)}/>
            <Route path="/admin/add_product" exact component= 
               {Auth(AddProduct,true)}/>
            <Route path="/admin/manage_categories" exact component= 
               {Auth(ManageCategories,true)}/>
            <Route path="/admin/site_info" exact component= 
               {Auth(ManageSite,true)}/>
            <Route path="/admin/add_file" exact component={Auth(AddFile,true)}/>        
            <Route path="/reset_password/:token" exact component={Auth(ResetPass,false)}/>
            <Route path="/reset_user" exact component={Auth(ResetUser,false)}/>
            <Route path="/product_detail/:id" exact component={Auth(ProductPage,null)}/>
            <Route path="/register" exact component={Auth(Register,false)}/>
            <Route path="/register_login" exact component={Auth(RegisterLogin,false)}/>
            <Route path="/shop" exact component={Auth(Shop,null)}/>
            <Route path="/" exact component={Auth(Home,null)}/>
            <Route component={Auth(PageNotFound)}/>
      </Switch>
        </Layout>

      )
    }

    export default Routes;

This is the auth file, that is used in the routes. And the ./../models/user file is taken from the server-side.

auth.js


const { User } = require('./../models/user');

let auth = (req,res,next) => {
    let token = req.cookies.w_auth;

    User.findByToken(token,(err,user)=>{
        if(err) throw err;
        if(!user) return res.json({
            isAuth: false,
            error: true
        });

        req.token = token;
        req.user = user;
        next();
    })

}


module.exports = { auth }

(Just for reference, here is the models/user file from the server-side)

/models/user.js

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const moment = require("moment");
const SALT_I = 10;
require('dotenv').config();

const userSchema = mongoose.Schema({
    email:{
        type:String,
        required: true,
        trim: true,
        unique: 1
    },
    password:{
        type:String,
        required: true,
        minlength: 5
    },
    name:{
        type:String,
        required: true,
        maxlength:100
    },
    lastname:{
        type:String,
        required: true,
        maxlength:100
    },
    cart:{
        type:Array,
        default: []
    },
    history:{
        type:Array,
        default: []
    },
    role:{
        type:Number,
        default:0
    },
    token:{
        type:String
    },
    resetToken:{
        type:String
    },
    resetTokenExp:{
        type:Number
    },
});

userSchema.pre('save',function(next){
    var user = this;

    if(user.isModified('password')){
        bcrypt.genSalt(SALT_I,function(err,salt){
            if(err) return next(err);

            bcrypt.hash(user.password,salt,function(err,hash){
                if(err) return next(err);
                user.password = hash;
                next();
            });
        })
    } else{
        next()
    }
})

userSchema.methods.comparePassword = function(candidatePassword,cb){
    bcrypt.compare(candidatePassword,this.password,function(err,isMatch){
        if(err) return cb(err);
        cb(null,isMatch)
    })
}

userSchema.methods.generateResetToken = function(cb){
    var user = this;

    crypto.randomBytes(20,function(err,buffer){
        var token = buffer.toString('hex');
        var today = moment().startOf('day').valueOf();
        var tomorrow = moment(today).endOf('day').valueOf();

        user.resetToken = token;
        user.resetTokenExp = tomorrow;
        user.save(function(err,user){
            if(err) return cb(err);
            cb(null,user);
        })
    })
}


userSchema.methods.generateToken = function(cb){
    var user = this;
    var token = jwt.sign(user._id.toHexString(),process.env.SECRET)

    user.token = token;
    user.save(function(err,user){
        if(err) return cb(err);
        cb(null,user);
    })
}

userSchema.statics.findByToken = function(token,cb){
    var user = this;

    jwt.verify(token,process.env.SECRET,function(err,decode){
        user.findOne({"_id":decode,"token":token},function(err,user){
            if(err) return cb(err);
            cb(null,user);
        })
    })
}



const User = mongoose.model('User',userSchema);

module.exports = { User }

tl;dr: So, is the user in the state coming from the /models/user file from the server? Wouldn't this not work once deployed?

Any suggestions or otherwise would be greatly appreciated.

SC4RECROW
  • 119
  • 1
  • 10
  • You are using Mongoose (see user.js), the middleware layer that will CREATE the collection for you in MongoDB. See https://stackoverflow.com/questions/28712248/difference-between-mongodb-and-mongoose – Kasey Chang May 26 '20 at 00:38
  • Yes. But how is that getting passed up to the front-end? The auth.js file (which is in the front-end) is importing a file from the back-end. If I were to deploy this, as is, wouldn't it not work? Since the front-end cannot just import a file from the back-end? – SC4RECROW May 26 '20 at 00:54
  • mapStatetoProps and connect has synchronized, for lack of a better word, your state, prop, and store (i.e. Mongoose/MongoDB). See end of user.js This may help: http://www.darrenbeck.co.uk/nodejs/react/reacttutorial-part4/ – Kasey Chang May 26 '20 at 00:57

0 Answers0