Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin authentication functionality + Lots of Authentication fixes #94

Merged
merged 7 commits into from
Feb 29, 2024
Merged
28 changes: 28 additions & 0 deletions backend/controllers/AdminController.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,33 @@ const createAdminMiddleware = async (req, res, next) => {
}
};

const authenticateAdmin = async (req, res, next) => {
try {
const {password} = req.body;
const {admin} = res.locals;

if (admin === undefined) {
return res.status(401).json({message: 'Unauthorized: Admin'});
}

const match = await bcrypt.compare(password, admin.password);

if (match) {
res.locals.data = {
'message': 'Login successful',
'status': 'success',
};
next();
} else {
res.status(401).json({message: 'Incorrect admin email or password.'});
}
} catch (err) {
console.log(err);
res.status(500).json({error: 'Internal Server Error'});

}
}

module.exports = {
getEventRequests,
getAllEventRequests,
Expand All @@ -181,4 +208,5 @@ module.exports = {
processEventRequest,
getAdminByEmail,
createAdminMiddleware,
authenticateAdmin,
};
6 changes: 4 additions & 2 deletions backend/controllers/AuthController.js
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably remove that log.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const signToken = async (req, res, next) => {
// Sign the token with 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});
res.cookie('auth', token, {secure: false});

console.log('Token:', token);

next();
};
Expand Down Expand Up @@ -62,7 +64,7 @@ const signAdminToken = async (req, res, next) => {
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});
res.cookie('auth_pim', token, {secure: false});

next();
};
Expand Down
17 changes: 16 additions & 1 deletion backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,22 @@ app.use(errorHandler({ dumbExceptions: true, showStack: true }));

// Allows cross origin requests
const cors = require('cors');
app.use(cors());

// Allowed origins
const allowedOrigins = ['http://localhost:3000', 'http://localhost:4000'];

// CORS options to dynamically match the allowed origins and allow credentials
const corsOptionsDelegate = function (req, callback) {
let corsOptions;
if (allowedOrigins.indexOf(req.header('Origin')) !== -1) {
corsOptions = { origin: true, credentials: true }; // Reflect (enable) the requested origin in the CORS response
} else {
corsOptions = { origin: false }; // Disable CORS for this request
}
callback(null, corsOptions); // Callback expects two parameters: error and options
};

app.use(cors(corsOptionsDelegate));

// Parses cookies attached to the client request object
const cookieParser = require('cookie-parser');
Expand Down
5 changes: 2 additions & 3 deletions backend/routes/AdminRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
processEventRequest,
getAdminByEmail,
createAdminMiddleware,
authenticateAdmin,
} = require('../controllers/AdminController');

const sendSuccessResponse = require('../middleware/successResponse');
Expand All @@ -37,9 +38,7 @@

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

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

Check failure

Code scanning / CodeQL

Missing rate limiting High

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

// 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.
Expand Down
4 changes: 1 addition & 3 deletions backend/routes/VendorRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
router.get('/:vendorId', getVendorById, sendSuccessResponse);

// Creates a new vendor
router.post('/', createVendor, (req, res) => {
res.status(200).json({status: 'success'});
});
router.post('/', createVendor, sendSuccessResponse);

Check failure

Code scanning / CodeQL

Missing rate limiting High

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

// Create Vendor event request
router.post('/events/request', verify('vendor'), createEventRequest, sendSuccessResponse);
Expand Down
24 changes: 10 additions & 14 deletions frontend/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,15 @@ import MockVendorService from './services/MockServices/MockVendorService.js';
import EventsService from './services/Events/EventsService.js';
import VendorsService from './services/Vendors/VendorsService.js';

import {handleLoginVendor} from './services/handleLogin.js';
import {handleRegister} from './services/handleRegister.js';

import HttpClient from './services/HttpClient.js';

// Import configuration variables
import config from './config.js';

// Import HttpClient
import HttpClient from './services/HttpClient.js';

let eventService;
let vendorService;
let httpClient;

if (config.environment == 'dev') {
MockVendorService.init();
Expand All @@ -49,15 +48,12 @@ if (config.environment == 'dev') {
// Load base url for the backend
const baseUrl = config.baseUrl;

// Create HttpClient
const httpClient = new HttpClient(baseUrl);
// Initialize HttpClient
httpClient = new HttpClient(baseUrl);

// Initilize Services
const eventsService = new EventsService(httpClient);
const vendorsService = new VendorsService(httpClient);

eventService = eventsService;
vendorService = vendorsService;
eventService = new EventsService(httpClient);
vendorService = new VendorsService(httpClient);
}

const router = createBrowserRouter([
Expand All @@ -71,12 +67,12 @@ const router = createBrowserRouter([
},
{
path: '/login',
element: <Login loginService={handleLoginVendor} />,
element: <Login vendorService={vendorService} />,

},
{
path: '/register',
element: <Register registerService={handleRegister} />,
element: <Register vendorService={vendorService} />,
},
{
path: '/reset_password',
Expand Down
9 changes: 0 additions & 9 deletions frontend/src/objects/Permission.js

This file was deleted.

32 changes: 5 additions & 27 deletions frontend/src/objects/User.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,19 @@
// import Permission from './Permission.js';
import Cookies from 'js-cookie';
import {jwtDecode} from 'jwt-decode';

/*
Storage mechanism for the currently logged-in user.
*/
export default class User {
constructor(id, name, email, isadmin, phone_number=null, website=null) {
constructor(id, name, email, isadmin, phoneNumber=null, website=null) {
this.id = id;
this.name = name;
this.email = email;
this.isadmin = isadmin;
this.phone_number = phone_number;
this.phoneNumber = phoneNumber;
this.website = website;
}

// isAdmin() {
// return this.permission === Permission.Admin;
// }

// isVendor() {
// return this.permission === Permission.Vendor;
// }

static createFromCookie() {
if (Cookies.get('auth_pim') != undefined) {
const cookie = Cookies.get('auth_pim');
const decode = jwtDecode(cookie);

return User(decode.admin_id, decode.name, decode.email, true, null, null);
} else if ( Cookies.get('auth') != undefined ) {
const cookie = Cookies.get('auth');
const decode = jwtDecode(cookie);

return User(decode.vendor_id, decode.name, decode.email, false, decode.phone_number, decode.website);
} else {
return undefined;
}
static newUserFromCookie(cookie, isadmin) {
const decode = jwtDecode(cookie);
return new User(decode.admin_id, decode.name, decode.email, isadmin, null, null);
}
}
21 changes: 11 additions & 10 deletions frontend/src/routes/login.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@

import React, {useState} from 'react';
import logo from './../assets/PIM_logo_black.png';
import {Link, useNavigate} from 'react-router-dom';
import {useContext} from 'react';
import {Context} from '../services/context';
import PropTypes from 'prop-types';
import User from '../objects/User';

export default function Login({loginService}) {
export default function Login({vendorService}) {
const [email, setEmail] = useState('');
const [pass, setPass] = useState('');
const navigate = useNavigate();
const {setMessage, setBad, setUser} = useContext(Context);

async function handleLogin() {
const data = {email: email, password: pass};
const loginResponse = await loginService(data);

console.log(loginResponse);
const loginResponse = await vendorService.authenticateVendor(data);

if (loginResponse != undefined) {
if (loginResponse.status == 200) {
setUser(User.createFromCookie());
setUser(vendorService.httpClient.user);
setBad(false);
setMessage('Logged in succesfully');
navigate('/events');
console.log('Logged in!');
console.log('Logged in as user: ', vendorService.httpClient.user);
} else if (loginResponse.status == 401) {
setBad(true);
setMessage('Bad Request. Check username and password.');
Expand Down Expand Up @@ -53,7 +49,7 @@ export default function Login({loginService}) {
<div className="m-2">
<input
className="p-1 rounded-lg w-3/4 drop-shadow-md"
placeholder="Username"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}>
</input>
</div>
Expand Down Expand Up @@ -87,5 +83,10 @@ export default function Login({loginService}) {
}

Login.propTypes = {
loginService: PropTypes.func.isRequired,
vendorService: PropTypes.shape( {
authenticateVendor: PropTypes.func.isRequired,
httpClient: PropTypes.shape( {
user: PropTypes.object,
}).isRequired,
}).isRequired,
};
15 changes: 10 additions & 5 deletions frontend/src/routes/register.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {Context} from '../services/context';
import Alert from '../components/alert.jsx';
import {useContext} from 'react';
import PropTypes from 'prop-types';
import Vendor from '../objects/Vendor.js';


export default function Register({registerService}) {
export default function Register({vendorService}) {
const [name, setName] = useState('');
const [pass, setPass] = useState('');
const [pass2, setPass2] = useState('');
Expand All @@ -19,9 +20,11 @@ export default function Register({registerService}) {
const navigate = useNavigate();

async function handleRegister() {
const data = {name: name, email: email, password: pass, website: website, phoneNumber: phone};
const vendor = new Vendor(name, email, website, phone);

if (await registerService(data)) {
const response = await vendorService.createVendor(vendor, pass);
console.log(response);
if (response) {
setBad(false);
setMessage('Registered succesfully');
console.log('Registered!');
Expand Down Expand Up @@ -60,7 +63,7 @@ export default function Register({registerService}) {
</div>

<div className="m-2">
<input className="p-1 rounded-lg w-3/4" placeholder="Reenter password" type="password" onChange={(e) => setPass2(e.target.value)}></input>
<input className="p-1 rounded-lg w-3/4" placeholder="Re-enter password" type="password" onChange={(e) => setPass2(e.target.value)}></input>
</div>

{pass !== pass2 && <Alert content="Passwords match" bad ={true}/>}
Expand All @@ -79,5 +82,7 @@ export default function Register({registerService}) {
}

Register.propTypes = {
registerService: PropTypes.func.isRequired,
vendorService: PropTypes.shape({
createVendor: PropTypes.func.isRequired,
}).isRequired,
};
24 changes: 24 additions & 0 deletions frontend/src/services/Admins/AdminsRepository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default class AdminsRepository {
constructor(httpClient) {
this.httpClient = httpClient;
}
async authenticateAdmin(adminData) {
try {
const response = await this.httpClient.axiosInstance.post('admins/login', adminData);
// this.httpClient.processCookie(response.headers['set-cookie'][0]);
return response;
} catch (error) {
console.error('Error logging in admin:');
}
}

async createAdmin(adminData) {
try {
const response = await this.httpClient.axiosInstance.post('/admins', adminData);
return response.data;
} catch (error) {
console.error('Error creating admin:');
throw error;
}
}
}
16 changes: 16 additions & 0 deletions frontend/src/services/Admins/AdminsService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import AdminsRepository from './AdminsRepository.js';

export default class AdminsService {
constructor(httpClient) {
this.adminsRepository = new AdminsRepository(httpClient);
}

async authenticateAdmin(adminData) {
const response = await this.adminsRepository.authenticateAdmin(adminData);
return response;
}

async createAdmin(adminData) {
return await this.adminsRepository.createAdmin(adminData);
}
}
Loading
Loading