Skip to content

Commit

Permalink
Merging updates to the admin router / controller.
Browse files Browse the repository at this point in the history
  • Loading branch information
nh602 committed Jan 31, 2024
2 parents bc2aeb4 + d457237 commit 5cd1879
Show file tree
Hide file tree
Showing 32 changed files with 592 additions and 502 deletions.
Binary file modified .DS_Store
Binary file not shown.
7 changes: 4 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": false,
}
"editor.formatOnSave":false,
"css.lint.unknownAtRules":"ignore",
}
27 changes: 0 additions & 27 deletions backend/.eslintrc.js

This file was deleted.

61 changes: 61 additions & 0 deletions backend/controllers/AdminController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const express = require('express');
const db = require('../database');

const bcrypt = require('bcryptjs');
const {hash, genSalt} = require('bcryptjs');

const getEventRequests = async (req, res, next) => {
try {
const requests = await db.manyOrNone(
Expand Down Expand Up @@ -112,6 +115,62 @@ const processEventRequest = async (req, res, next) => {
}
}

// Middleware given an email in the body, retireves the given admin
// account or returns an error
const getAdminByEmail = async (req, res, next) => {
try {
const data = await db.oneOrNone('SELECT * FROM Admins WHERE email = $1', [
req.body.email,
]);

if (data) {
res.locals.admin = data;

next();
} else {
res.status(401).json({message: 'Email not found.'});
}
} catch (err) {
console.error(err);
res.status(500).json({error: 'Internal Server Error'});
}
};

// Middleware to create a new admin account.
// If used, should be an admin route. i.e. allow Lluvia
// to create a new admin account if wanted.
const createAdmin = async (name, email, password) => {
const salt = await genSalt(10);
const hashed = await hash(password, salt);

return await db.one(
'INSERT INTO Admins (name, email, password) VALUES ($1, $2, $3) RETURNING *',
[name, email, hashed],
);
};

const createAdminMiddleware = async (req, res, next) => {
try {
const {name, email, password} = req.body;

if (name === undefined || email === undefined || password === undefined) {
console.log(req.body);
res.status(400).json({error: 'Missing required fields'});
return;
}

const data = await createAdmin(name, email, password);

res.locals.data = data;

next();
} catch (err) {
console.error(err);
res.status(500).json({error: 'Internal Server Error'});
return;
}
};

module.exports = {
getEventRequests,
getAllEventRequests,
Expand All @@ -120,4 +179,6 @@ module.exports = {
createVendorViolation,
deleteVendorViolation,
processEventRequest,
getAdminByEmail,
createAdminMiddleware,
};
95 changes: 88 additions & 7 deletions backend/controllers/AuthController.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
const jwt = require('jsonwebtoken');

// This middleware will be used to sign the token after a user logs in
const db = require('../database');

// TODO: Set cookies to secure when https is ready?

// This middleware will be used to sign the token after a vendor logs in
const signToken = async (req, res, next) => {
if (res.locals.vendor === undefined) {
return res.status(401).json({message: 'Unauthorized'});
}

// Sign the token with JWT_SECRET
const token = jwt.sign(res.locals.vendor, process.env.JWT_SECRET);
const token = await jwt.sign(res.locals.vendor, process.env.JWT_SECRET);
// Return the token in a cookie
res.cookie('auth', token, {httpOnly: true, secure: false});

next();
};

// This middleware will be used to verify the token sent by the client
// This middleware will be used to verify the token sent by the vendor
const verifyToken = async (req, res, next) => {
// Retrieve the token from the cookie
const token = req.cookies.auth;
Expand All @@ -20,11 +28,84 @@ const verifyToken = async (req, res, next) => {
if (err) {
res.status(401).json({message: 'Unauthorized'});
} else {
// Add the decoded object to res.locals
res.locals.vendor = decoded;
next();
// Update cookie data from database
db.oneOrNone('SELECT * FROM vendors WHERE vendor_id = $1', [decoded.vendor_id], (result, err) => {
if (err) {
res.status(500).json({message: 'Internal Server Error'});
} else if (result === null) {
res.status(401).json({message: 'Unauthorized'});
}

res.locals.vendor = result;
// Remove the password - isn't needed for any other processes.
delete res.locals.vendor['password'];

// Sign new token and continue
signToken(req, res, next);
});
}
});
};

// This middleware will be used to sign the token after an admin logs in
const signAdminToken = async (req, res, next) => {
if (res.locals.admin === undefined) {
return res.status(401).json({message: 'Unauthorized'});
}

// Sign the token with JWT_SECRET
const token = await jwt.sign(res.locals.admin, process.env.JWT_SECRET);

// Return the token in a cookie
res.cookie('auth_pim', token, {httpOnly: true, secure: false});

next();
};

const verifyAdminToken = async (req, res, next) => {
// Retrieve the token from the cookie
const token = req.cookies.auth_pim;

// Verify the token with JWT_SECRET
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({message: 'Unauthorized'});
} else {
// Keep the cookie up to date with the database
db.oneOrNone('SELECT * FROM admins WHERE admin_id = $1', [decoded.admin_id], (result, err) => {
if (err) {
console.log(err);
return res.status(500).json({message: 'Internal Server Error'});
} else if (result === undefined) {
return res.status(401).json({message: 'Unauthorized'});
}

console.log(result);

res.locals.admin = result;
delete res.locals.admin['password'];

// Sign new token and continue
signAdminToken(req, res, next);
});
}
});
};

module.exports = {signToken, verifyToken};
// Returns a middleware to verify the user has the correct permissions.
// Setting a route that only admins can access.
// USAGE: router.get('/route', verify('admin'), controller.method);
const verify = (privilege) => {
if (privilege === 'admin') {
// Check the admin token
return verifyAdminToken;
} else if (privilege === 'vendor') {
// Check the vendor token
return verifyToken;
} else {
// You shouldn't need to set privilege for any other use case.
throw new Error('Privilege should be either vendor or admin.');
}
};

module.exports = {signToken, verifyToken, verifyAdminToken, signAdminToken, verify};
5 changes: 4 additions & 1 deletion backend/controllers/EventController.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ const getEventById = async (req, res, next) => {
const createEvent = async (req, res, next) => {
const {name, location, datetime, description, capacity} = req.body;
try {
await db.none(`
const event = await db.one(`
INSERT INTO Events (name, location, datetime, description, vendor_capacity)
VALUES ($1, $2, $3, $4, $5)
RETURNING *;
`, [name, location, datetime, description, capacity]);

// Returns the created event
res.locals.data = event;

next();
} catch (error) {
console.error('Error creating event:', error);
Expand Down
6 changes: 3 additions & 3 deletions backend/controllers/VendorController.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const getVendorById = async (req, res, next) => {
// Registers the vendor in the database
const createVendor = async (req, res, next) => {
// Get the values from the request body
const {name, email, phone_number, password, website} = req.body;
const {name, email, phoneNumber, password, website} = req.body;

// Checks if the required fields are present
if (!password || !email || !name) {
Expand All @@ -107,7 +107,7 @@ const createVendor = async (req, res, next) => {
passwordHash = await hash(password, salt);
} catch (err) {
console.log(err);
res.status(500).json({error: err});
res.status(495).json({error: err});
return;
}

Expand All @@ -121,7 +121,7 @@ const createVendor = async (req, res, next) => {
password, \
website\
) VALUES ($1, $2, $3, $4, $5)',
[name, email, phone_number, passwordHash, website],
[name, email, phoneNumber, passwordHash, website],
);
} catch (err) {
// Duplicate emails are not allowed
Expand Down
1 change: 1 addition & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ app.use(cookieParser());
const VendorRouter = require('./routes/VendorRouter');
const AdminRouter = require('./routes/AdminRouter');
const EventRouter = require('./routes/EventRouter');
const AdminRouter = require('./routes/AdminRouter');

app.use('/vendors', VendorRouter);
app.use('/events', EventRouter);
Expand Down
1 change: 1 addition & 0 deletions backend/middleware/successResponse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// successResponse.js
function sendSuccessResponse(req, res) {
if (res.locals.data) {
// console.log('Data in res.locals', res.locals.data);
res.status(200).json(res.locals.data);
} else {
// Fallback if no data is set in res.locals, but middleware is called
Expand Down
22 changes: 21 additions & 1 deletion backend/routes/AdminRouter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
const express = require('express');
const router = express.Router();

// Auth Controller Imports
const {
verifyAdminToken,
signAdminToken,
verify,
} = require('../controllers/AuthController');

// Admin Controller Imports
const {
getEventRequests,
getAllEventRequests,
Expand All @@ -9,6 +17,8 @@ const {
createVendorViolation,
deleteVendorViolation,
processEventRequest,
getAdminByEmail,
createAdminMiddleware,
} = require('../controllers/AdminController');

const sendSuccessResponse = require('../middleware/successResponse');
Expand All @@ -27,4 +37,14 @@ router.post('/violations/:vendorId', createVendorViolation, sendSuccessResponse)

router.delete('/violations/:violationId', deleteVendorViolation, sendSuccessResponse);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

module.exports = router;
router.post('/login', getAdminByEmail, signAdminToken, (req, res) => {
res.status(200).json({status: 'success'});
});

// UNFINISHED: Create an admin account
// Useful for creating an admin account for testing purposes. Password in database needs to be hashed for login to work properly.
// router.post('/', createAdminMiddleware, (req, res) => {
// res.status(200).json({status: 'success', admin: res.locals.data});
// });

module.exports = router;
5 changes: 3 additions & 2 deletions backend/routes/EventRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
updateEvent,
} = require('../controllers/EventController');
const sendSuccessResponse = require('../middleware/successResponse');
const {verify} = require('../controllers/AuthController');

// Get all events
router.get('/', getAllEvents, sendSuccessResponse);
Expand All @@ -15,9 +16,9 @@ router.get('/', getAllEvents, sendSuccessResponse);
router.get('/:event_id', getEventById, sendSuccessResponse);

// Create a new event
router.post('/', createEvent, sendSuccessResponse);
router.post('/', verify('admin'), createEvent, sendSuccessResponse);

// Update an existing event
router.put('/:event_id', updateEvent, sendSuccessResponse);
router.put('/:event_id', verify('admin'), updateEvent, sendSuccessResponse);

module.exports = router;
10 changes: 5 additions & 5 deletions backend/routes/VendorRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const {
} = require('../controllers/VendorController');
const sendSuccessResponse = require('../middleware/successResponse');
const {
verifyToken,
signToken,
verify,
} = require('../controllers/AuthController');

// Logs in vendor
Expand All @@ -32,16 +32,16 @@ router.post('/', createVendor, (req, res) => {
});

// Create Vendor event request
router.post('/events/request', createEventRequest, sendSuccessResponse);
router.post('/events/request', verify('vendor'), createEventRequest, sendSuccessResponse);

// Get Vendor event request
router.get('/events/request', getEventRequest, sendSuccessResponse);
router.get('/events/request', verify('admin'), getEventRequest, sendSuccessResponse);

// Edit vendor by id
// This probably should be an admin-protected route. How does that work?
router.put('/:vendorId', verifyToken, updateVendor, sendSuccessResponse);
router.put('/:vendorId', verify('admin'), updateVendor, sendSuccessResponse);

// Route for vendor to update themself. ID is retrieved from the token.
router.put('/', verifyToken, updateAuthenticatedVendor, sendSuccessResponse);
router.put('/', verify('vendor'), updateAuthenticatedVendor, sendSuccessResponse);

module.exports = router;
Loading

0 comments on commit 5cd1879

Please sign in to comment.