Copyright Derek O'Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.
Server-side sessions are needed if a user needs to be logged in to access a database collection or any other resource on the server-side.
Session IDs tie the client-side and server-side together. A session ID is generated on the server-side. It is sent as a cookie from the server-side to the client-side when a user opens a website. It can be sent the other way (from client-side to server-side) as part of the header data in an axios function call.
In order to use sessions, we must create a private session key. This should be stored in the server/config/.env file, as shown below:
... # Keys
SESSION_PRIVATE_KEY = yourRandomKey ...
We must tie the SESSION_PRIVATE_KEY to an express-session in the server/server.js file, as shown below:
... app.use(require(`express-session`)({
secret: process.env.SESSION_PRIVATE_KEY,
resave: false,
cookie: {secure: false, maxAge: 60000},
saveUninitialized: true
})) ...
In order to use sessions, we must include the express-session package. Use npm to install the express-session package.
On the server-side, session data is stored in the req.session property that is passed in the header of client-side axios function calls.
We can add our own additional properties to req.session. In the code below, we add a user property (req.session.user). We should set the req.session.user property whenever a user successfully logs in.
In the code below, the user property is initialised to contain an email and accessLevel property. This means that the properties req.session.user.email and req.session.user.accessLevel will be available on the server-side for any logged in user.
router.post(`/users/login/:email/:password`, (req,res) =>
{ ... req.session.user = {email:data.email, accessLevel:data.accessLevel}
res.json({name:data.name, accessLevel:data.accessLevel}) ... }
When a user logs out, the session should be destroyed on the server-side, as shown below:
router.post(`/users/logout`, (req,res) =>
{
req.session.destroy()
res.json({})
})
All router code that accesses restricted resources, such as database collections, should do server-side validation to ensure that the user is allowed to access the resource.
The req.session.user property that is created in the login code above can be used anywhere on the server-side to check if a user is logged in. The code below can be used to check that the req.session.user property exists. If req.session.user does not exist, then the user is not logged in.
if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) }
The code below checks on the server-side that a user is logged in before allowing the user to add a new car to the 'cars' collection.
In the login code described above, an accessLevel property is added to the req.session.user property. We can check this property against the required access level that is needed to access a resource. The code below checks that the logged-in user is an administrator before letting them add a new car to the 'cars' collection.
// Add new record
router.post(`/cars`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
if(req.session.user.accessLevel !== `undefined` && req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
{
carsModel.create(req.body)
.then(data => res.json(data))
.catch(err => next(err))
}
else
{
next(createError(403, `User is not an administrator, so they cannot delete records`))
}
}
})
Session IDs can be sent from the client-side to the server-side during the next axios function call. This allows the server-side to tie the client-side to a given session. In order to do this, we only have to include the line of code that is shown below before we make each axios call.
axios.defaults.withCredentials = true
Open the client_side_session_storage project from the previous section in these notes. Change the code so that it uses sessions to validate that users are logged in at the correct access level before allowing them to interact with the 'cars' collection.
The full project code for the "Cars" Worked Example that is described below can be downloaded from this link.
In this example, we implement server-side sessions and restrict server-side access to user's who have the appropriate access level.
Other than the inclusion of axios.defaults.withCredentials = true before each axios() method, the client-side code does not change.
import React, {useState} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"
export const Register = props =>
{
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [isRegistered, setIsRegistered] = useState(false)
const handleNameChange = e =>
{
setName(e.target.value)
}
const handleEmailChange = e =>
{
setEmail(e.target.value)
}
const handlePasswordChange = e =>
{
setPassword(e.target.value)
}
const handleConfirmPasswordChange = e =>
{
setConfirmPassword(e.target.value)
}
const handleSubmit = (e) =>
{
e.preventDefault()
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/register/${name}/${email}/${password}`)
.then(res =>
{
sessionStorage.name = res.data.name
sessionStorage.accessLevel = res.data.accessLevel
setIsRegistered(true)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}
return (
<form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">
{isRegistered ? <Redirect to="/DisplayAllCars"/> : null}
<h2>New User Registration</h2>
<input
name = "name"
type = "text"
placeholder = "Name"
autoComplete="name"
value = {name}
onChange = {handleNameChange}
autoFocus
/><br/>
<input
name = "email"
type = "email"
placeholder = "Email"
autoComplete="email"
value = {email}
onChange = {handleEmailChange}
/><br/>
<input
name = "password"
type = "password"
placeholder = "Password"
autoComplete="password"
title = "Password must be at least ten-digits long and contains at least one lowercase letter, one uppercase letter, one digit and one of the following characters (£!#€$%^&*)"
value = {password}
onChange = {handlePasswordChange}
/><br/>
<input
name = "confirmPassword"
type = "password"
placeholder = "Confirm password"
autoComplete="confirmPassword"
value = {confirmPassword}
onChange = {handleConfirmPasswordChange}
/><br/><br/>
<Button value="Register New User" className="green-button" onClick={handleSubmit} />
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"
export const Login = props =>
{
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoggedIn, setIsLoggedIn] = useState(false)
const handleEmailChange = e =>
{
setEmail(e.target.value)
}
const handlePasswordChange = e =>
{
setPassword(e.target.value)
}
const handleSubmit = e =>
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/login/${email}/${password}`)
.then(res =>
{
sessionStorage.name = res.data.name
sessionStorage.accessLevel = res.data.accessLevel
setIsLoggedIn(true)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}
return (
<form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">
<h2>Login</h2>
{isLoggedIn ? <Redirect to="/DisplayAllCars"/> : null}
<input
type = "email"
name = "email"
placeholder = "Email"
autoComplete="email"
value={email}
onChange={handleEmailChange}
/><br/>
<input
type = "password"
name = "password"
placeholder = "Password"
autoComplete="password"
value={password}
onChange={handlePasswordChange}
/><br/><br/>
<Button value="Login" className="green-button" onClick={handleSubmit}/>
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {ACCESS_LEVEL_GUEST, SERVER_HOST} from "../config/global_constants"
export const Logout = props =>
{
const [isLoggedIn, setIsLoggedIn] = useState(true)
const handleSubmit = e =>
{
e.preventDefault()
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/logout`)
.then(res =>
{
sessionStorage.clear()
sessionStorage.name = "GUEST"
sessionStorage.accessLevel = ACCESS_LEVEL_GUEST
setIsLoggedIn(false)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}
return (
<div>
{!isLoggedIn ? <Redirect to="/DisplayAllCars"/> : null}
<Button value="Log out" className="red-button" onClick={handleSubmit}/>
</div>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState, useEffect} from "react"
import {Link} from "react-router-dom"
import axios from "axios"
import {CarTable} from "./CarTable"
import {Logout} from "./Logout"
import {ACCESS_LEVEL_GUEST, ACCESS_LEVEL_ADMIN, SERVER_HOST} from "../config/global_constants"
export const DisplayAllCars = props =>
{
const [cars, setCars] = useState([])
useEffect(() =>
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.get(`${SERVER_HOST}/cars`)
.then(res =>
{
setCars(res.data)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}, [])
return (
<div className="form-container">
{sessionStorage.accessLevel > ACCESS_LEVEL_GUEST ?
<div className="logout">
<Logout/>
</div>
:
<div>
<Link className="green-button" to={"/Login"}>Login</Link>
<Link className="blue-button" to={"/Register"}>Register</Link>
<Link className="red-button" to={"/ResetDatabase"}>Reset Database</Link> <br/><br/><br/></div>
}
<div className="table-container">
<CarTable cars={cars} />
{sessionStorage.accessLevel >= ACCESS_LEVEL_ADMIN ?
<div className="add-new-car">
<Link className="blue-button" to={"/AddCar"}>Add New Car</Link>
</div>
:
null
}
</div>
</div>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {ACCESS_LEVEL_ADMIN, SERVER_HOST} from "../config/global_constants"
export const AddCar = props =>
{
const [model, setModel] = useState('')
const [colour, setColour] = useState('')
const [year, setYear] = useState('')
const [price, setPrice] = useState('')
const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(sessionStorage.accessLevel < ACCESS_LEVEL_ADMIN)
const handleModelChange = e =>
{
setModel(e.target.value)
}
const handleColourChange = e =>
{
setColour(e.target.value)
}
const handleYearChange = e =>
{
setYear(e.target.value)
}
const handlePriceChange = e =>
{
setPrice(e.target.value)
}
const handleSubmit = (e) =>
{
e.preventDefault()
const carObject = {
model: model,
colour: colour,
year: year,
price: price
}
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/cars`, carObject)
.then(res =>
{
setRedirectToDisplayAllCars(true)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}
return (
<div className="form-container">
{redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}
<form>
<label>Model</label>
<input autoFocus type="text" name="model" value={model} onChange={handleModelChange} />
<label>Colour</label>
<input type="text" name="colour" value={colour} onChange={handleColourChange} />
<label>Year</label>
<input type="text" name="year" value={year} onChange={handleYearChange} />
<label>Price</label>
<input type="text" name="price" value={price} onChange={handlePriceChange} /><Button value="Update" className="green-button" onClick={handleSubmit}/>
<Button value="Add" className="green-button" onClick={handleSubmit}/>
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
</div>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState, useEffect} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import {Button} from "../components/Button"
import {ACCESS_LEVEL_NORMAL_USER, SERVER_HOST} from "../config/global_constants"
export const EditCar = props =>
{
const [model, setModel] = useState('')
const [colour, setColour] = useState('')
const [year, setYear] = useState('')
const [price, setPrice] = useState('')
const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(sessionStorage.accessLevel < ACCESS_LEVEL_NORMAL_USER)
useEffect(() =>
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.get(`${SERVER_HOST}/cars/${props.match.params.id}`)
.then(res =>
{
setModel(res.data.model)
setColour(res.data.colour)
setYear(res.data.year)
setPrice(res.data.price)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}, [props.match.params.id])
const handleModelChange = e =>
{
setModel(e.target.value)
}
const handleColourChange = e =>
{
setColour(e.target.value)
}
const handleYearChange = e =>
{
setYear(e.target.value)
}
const handlePriceChange = e =>
{
setPrice(e.target.value)
}
const handleSubmit = e =>
{
e.preventDefault()
const carObject = {
model: model,
colour: colour,
year: year,
price: price
}
axios.defaults.withCredentials = true // needed for sessions to work
axios.put(`${SERVER_HOST}/cars/${props.match.params.id}`, carObject)
.then(res =>
{
setRedirectToDisplayAllCars(true)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}
return (
<div className="form-container">
{redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}
<form>
<label>Model</label>
<input autoFocus type="text" name="model" value={model} onChange={handleModelChange} />
<label>Colour</label>
<input type="text" name="colour" value={colour} onChange={handleColourChange} />
<label>Year</label>
<input type="text" name="year" value={year} onChange={handleYearChange} />
<label>Price</label>
<input type="text" name="price" value={price} onChange={handlePriceChange} /><Button value="Update" className="green-button" onClick={handleSubmit}/>
<Button value="Update" className="green-button" onClick={handleSubmit}/>
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
</div>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {useState, useEffect} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"
import {SERVER_HOST} from "../config/global_constants"
export const DeleteCar = props =>
{
const [redirectToDisplayAllCars, setRedirectToDisplayAllCars] = useState(false)
useEffect(() =>
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.delete(`${SERVER_HOST}/cars/${props.match.params.id}`)
.then(res =>
{
setRedirectToDisplayAllCars(true)
})
.catch(err => console.log(`${err.response.data}\n${err}`))
}, [props.match.params.id])
return (
<div>
{redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}
</div>
)
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
A session's ID (which is shared on the client-side via a cookie) and its server-side details are accessible via the req.session object, which is one of the parameters of the various router methods. A session property is created and becomes available for subsequent use as soon as it is first assigned a value. In the "Cars" example, a session property called user has been assigned a sub-property called accessLevel.
# This file holds global constants that are visible on the Server-side # Database DB_NAME = D01234567 # Access Levels ACCESS_LEVEL_GUEST = 0 ACCESS_LEVEL_NORMAL_USER = 1 ACCESS_LEVEL_ADMIN = 2 # Keys SESSION_PRIVATE_KEY = yourRandomKey # Salt length of encryption of user passwords # The salt length should be 16 or higher for commercially released code # It has been set to 3 here, so that the password will be generated faster PASSWORD_HASH_SALT_ROUNDS = 3 # Port SERVER_PORT = 4000 # Local Host LOCAL_HOST = http://localhost:3000
We create a private SESSION_PRIVATE_KEY for our session.
// Server-side global variables
require(`dotenv`).config({path:`./config/.env`})
// Database
require(`./config/db`)
// Express
const express = require(`express`)
const app = express()
app.use(require(`express-session`)({
secret: process.env.SESSION_PRIVATE_KEY,
resave: false,
cookie: {secure: false, maxAge: 60000},
saveUninitialized: true
}))
app.use(require(`body-parser`).json())
app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST}))
// Routers
app.use(require(`./routes/cars`))
app.use(require(`./routes/users`))
// Port
app.listen(process.env.SERVER_PORT, () =>
{
console.log(`Connected to port ` + process.env.SERVER_PORT)
})
// Error 404
app.use((req, res, next) => {next(createError(404))})
// Other errors
app.use(function (err, req, res, next)
{
console.error(err.message)
if (!err.statusCode)
{
err.statusCode = 500
}
res.status(err.statusCode).send(err.message)
})
We use the express-session package.
app.use(require(`express-session`)({ secret: process.env.SESSION_PRIVATE_KEY, resave: false, cookie: {secure: false, maxAge: 60000}, saveUninitialized: true }))
Install the express-session package.
const router = require(`express`).Router()
const createError = require('http-errors')
const usersModel = require(`../models/users`)
const bcrypt = require('bcryptjs'); // needed for password encryption
// IMPORTANT
// Obviously, in a production release, you should never have the code below, as it allows a user to delete a database collection
// The code below is for development testing purposes only
router.post(`/users/reset_user_collection`, (req, res, next) =>
{
usersModel.deleteMany({})
.then(data =>
{
const adminPassword = `123-qwe_QWE`
bcrypt.hash(adminPassword, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (error, hash) =>
{
usersModel.create({name: "Administrator", email: "admin@admin.com", password: hash, accessLevel:parseInt(process.env.ACCESS_LEVEL_ADMIN)})
.then(createData => res.json(createData))
.catch(err => next(createError(500, `Failed to create Admin user for testing purposes`)))
})
})
.catch(err => next(err))
})
router.post(`/users/register/:name/:email/:password`, (req, res, next) =>
{
// If a user with this email does not already exist, then create new user
usersModel.findOne({email: req.params.email})
.then(uniqueData =>
{
if(uniqueData)
{
next(createError(403, `User already exists`))
}
else
{
bcrypt.hash(req.params.password, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (error, hash) =>
{
usersModel.create({name: req.params.name, email: req.params.email, password: hash})
.then(data =>
{
req.session.user = {email: data.email, accessLevel:data.accessLevel}
res.json({name: data.name, accessLevel:data.accessLevel})
})
.catch(err => next(createError(409, `User was not registered`)))
})
}
})
.catch(err => next(err))
})
router.post(`/users/login/:email/:password`, (req, res, next) =>
{
usersModel.findOne({email: req.params.email})
.then(data =>
{
bcrypt.compare(req.params.password, data.password, (err, result) =>
{
if(result)
{
req.session.user = {email: data.email, accessLevel:data.accessLevel}
res.json({name: data.name, accessLevel: data.accessLevel})
}
else
{
next(createError(403, `User is not logged in`))
}
})
})
.catch(err => next(createError(403, `User is not logged in`)))
})
router.post(`/users/logout`, (req,res) =>
{
req.session.destroy()
res.json({})
})
module.exports = router
When a user registers, their email and accessLevel are stored in the session. This session information will then be available on the server-side for other methods to access.
router.post(`/users/register/:name/:email/:password`, (req, res, next) =>
{
// If a user with this email does not already exist, then create new user
usersModel.findOne({email: req.params.email})
.then(uniqueData =>
{
if(uniqueData)
{
next(createError(403, `User already exists`))
}
else
{
bcrypt.hash(req.params.password, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (error, hash) =>
{
usersModel.create({name: req.params.name, email: req.params.email, password: hash})
.then(data =>
{
req.session.user = {email: data.email, accessLevel:data.accessLevel}
res.json({name: data.name, accessLevel:data.accessLevel})
})
.catch(err => next(createError(409, `User was not registered`)))
})
}
})
.catch(err => next(err))
})
Likewise, when a user logs in, their email and accessLevel are stored in the session. This session information will then be available on the server-side for other methods to access.
router.post(`/users/login/:email/:password`, (req,res,next) =>
{
...
req.session.user = {email: data.email, accessLevel:data.accessLevel}
...
})
When a user logs out, the session is destroyed and it is no longer accessible on the server-side.
router.post(`/users/logout`, (req,res) => {
req.session.destroy()
res.json({})
})
const router = require(`express`).Router()
const createError = require('http-errors')
const carsModel = require(`../models/cars`)
// read all records
router.get(`/cars`, (req, res, next) =>
{
//user does not have to be logged in to see car details
carsModel.find({})
.then(data => res.json(data))
.catch(err => next(err))
})
// Read one record
router.get(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
carsModel.findById(req.params.id)
.then(data => res.json(data))
.catch(err => next(err))
}
})
// Add new record
router.post(`/cars`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
{
carsModel.create(req.body)
.then(data => res.json(data))
.catch(err => next(err))
}
else
{
next(createError(403, `User is not an administrator, so they cannot delete records`))
}
}
})
// Update one record
router.put(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
carsModel.findByIdAndUpdate(req.params.id, {$set: req.body})
.then(data => res.json(data))
.catch(err => next(err))
}
})
// Delete one record
router.delete(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
{
carsModel.findByIdAndDelete(req.params.id)
.then(data => res.json(data))
.catch(err => next(err))
}
else
{
next(createError(403, `User is not an administrator, so they cannot delete records`))
}
}
})
module.exports = router
A session is only set up when a user successfully logs in.
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
// Read one record
router.get(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
carsModel.findById(req.params.id)
.then(data => res.json(data))
.catch(err => next(err))
}
})
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
We can test the accessLevel of a user by checking if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
// Add new record
router.post(`/cars`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
{
carsModel.create(req.body)
.then(data => res.json(data))
.catch(err => next(err))
}
else
{
next(createError(403, `User is not an administrator, so they cannot delete records`))
}
}
})
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
// Update one record
router.put(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
carsModel.findByIdAndUpdate(req.params.id, {$set: req.body})
.then(data => res.json(data))
.catch(err => next(err))
}
})
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
We can test the accessLevel of a user by checking if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
// Delete one record
router.delete(`/cars/:id`, (req, res, next) =>
{
if(typeof req.session.user === `undefined`)
{
next(createError(403, `User is not logged in`))
}
else
{
if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
{
carsModel.findByIdAndDelete(req.params.id)
.then(data => res.json(data))
.catch(err => next(err))
}
else
{
next(createError(403, `User is not an administrator, so they cannot delete records`))
}
}
})
Write code to incorporate the login (from here), registration (from here) and validation (from here), as shown here.
Add MongoDB field validation to the above code. You should provide appropriate client-side error messages.
Why should you validate at all three of the client-side, the server-side routes, and the mongoDB?
Copyright Derek O' Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.