Skip to content

Commit

Permalink
Added development and production compatibility.
Browse files Browse the repository at this point in the history
Added compatibility for synchronous developmental and production testing
  • Loading branch information
wyattmog committed Nov 7, 2024
1 parent aa15b17 commit ae1a2cf
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 120 deletions.
8 changes: 8 additions & 0 deletions Backend/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DB1_HOST = '10.0.1.184'
DB1_USER = 'admin'
DB1_PASSWORD = 'LinusandTheo2021!'
DB1_DATABASE = 'instagram_tool'
ROUTE = '/home/opc/InstagramTool/Frontend'

ACCESS_TOKEN_SECRET = '331ea4ef72ccd393a9edf3bf6ac0df75affb7de5ff2a8462cf3681baadd1c1c1a2c1a64d278c35f41baeaa6a14167fc6f1c439791d97ea122f5ac0631f5407e7'
REFRESH_TOKEN_SECRET = '8d0aa255929ad2437319f1ca5ae388d0f703881d5894ea934466ffd3a9bb7008bebc7ca5b2b6d9da44b5845eb7b6938a55cc52ea933838522fdbf47afcd11808'
199 changes: 79 additions & 120 deletions Backend/server.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const express = require('express')
const multer = require('multer')
const jsdom = require('jsdom')
const yauzl = require('yauzl');
const { JSDOM } = jsdom;
const https = require('https')
const fs = require('fs')
const { JSDOM } = jsdom;
const bcrypt = require('bcryptjs')
const dotenv = require('dotenv')
const jwt = require('jsonwebtoken')
const cors = require('cors')
const cookieParser = require('cookie-parser')
<<<<<<< Updated upstream
dotenv.config()
=======
const result = dotenv.config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
Expand All @@ -18,43 +18,50 @@ if (result.error) {
}
const isProduction = process.env.NODE_ENV === 'production';

const logoutCookieOptions = isProduction ? {
httpOnly: true, // Ensures the cookie is not accessible via JavaScript
secure: isProduction, // Ensures the cookie is only sent over HTTPS
sameSite: 'None', // Ensures the cookie is sent with cross-site requests
domain: '.instagram-tool.duckdns.org', // Make sure the domain has a dot in front
path: '/', // Ensure this matches the path where the cookie was set
} : {
httpOnly: true,
sameSite: 'Strict',
secure: isProduction, // Set secure cookies only in production
}
const allowedOrigins = isProduction ?
['https://wyattmog.github.io'] :
['http://localhost:5500'];


>>>>>>> Stashed changes
const mysql = require('mysql2')
const app = express()
const port = 8383
// const host = process.env.HOST;


app.use(express.static('/Users/wyattmogelson/Coding/InstagramTool/Frontend'))
app.use(express.json())
app.use(cookieParser())
// console.log(result)
app.use(cors({
origin: allowedOrigins, // Adjust as needed
credentials: true
}))

const storage = multer.memoryStorage({
storage: multer.memoryStorage() ,
limits: { fileSize: 10 * 1024 * 1024 }
limits: { fileSize: 5 * 1024 * 1024 }
})
// Use enviornment variables because it prevents hard coding
// authentication, and allows user to change the value without
// coding it in javascript.


// Now you can access your environment variables

// coding it in javascript. Sensitive info like password also
// shouldn't be hardcoded.
const pool = mysql.createPool({
host: process.env.DB1_HOST,
user: process.env.DB1_USER,
password: process.env.DB1_PASSWORD,
database: process.env.DB1_DATABASE

}).promise()

const uploads = multer({storage: storage})
// Get request that first authenticates user, then parses and sends the request information to client browser.
// This communicates from backend to front end
// Will use to display information after processing in sql
app.get('/data', authenticateToken, async (req,res) => {
const result = await parse(req.user)
if (result[0].length === 0) {
Expand All @@ -64,96 +71,54 @@ app.get('/data', authenticateToken, async (req,res) => {
res.json(result)
}
})
// Error handler for multer in order to catch and send information back to client browser
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
return res.status(400).send('An error occurred during the file upload.');
return res.status(400).send('An error occurred during the file upload.'); // Send a generic error message
} else if (err) {
// An unknown error occurred when uploading.
return res.status(400).send('An error occurred during the request.');
return res.status(400).send('An error occurred during the request.'); // Send a generic error message
}
next();
});

// Extracts html files from the zip file
// Puts target files in array to return
async function extractHtml(zipBuffer, targetFiles) {
return new Promise((resolve, reject) => {
const extractedFiles = [];
yauzl.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zipfile) => {
if (err) return reject(err);
zipfile.readEntry();
zipfile.on("entry", (entry) => {
if (targetFiles.includes(entry.fileName)) {
// Read the entry into memory
zipfile.openReadStream(entry, (err, readStream) => {
if (err) return reject(err);

let buffers = [];
readStream.on('data', (chunk) => {
buffers.push(chunk); // Collect chunks of data
});
readStream.on('end', () => {
const htmlBuffer = Buffer.concat(buffers); // Concatenate the collected buffers
extractedFiles.push({
originalname: entry.fileName,
buffer: htmlBuffer // Store the HTML file contents in memory
});
zipfile.readEntry(); // Read the next entry
});
});
} else {
zipfile.readEntry();
}
});
zipfile.on('end', () => {
resolve(extractedFiles); // Resolve the promise with the collected HTML files
});
});
})
}
// Recieves post request from frontend
// Puts followers file in /followers directory
// Puts following file in /following directory
// Uses JSDOM to parse and query and get all hyperlink content
// For both following and follower links, puts
// both into respective arrays.
app.post('/uploads', authenticateToken, uploads.single('zipfile'), async (req, res) => {
const zipBuffer = req.file.buffer;
const targetFiles = ['connections/followers_and_following/followers_1.html', 'connections/followers_and_following/following.html'];
app.post('/uploads', authenticateToken, uploads.array('files'), async (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(500).send('No files uploaded or invalid file type.');
}
try {
const htmlFiles = await extractHtml(zipBuffer, targetFiles);
// Checks if needed files were found in zip file
const followersFile = htmlFiles.find(file => (file.originalname === 'connections/followers_and_following/followers_1.html'));
const followingFile = htmlFiles.find(file => (file.originalname === 'connections/followers_and_following/following.html'));
const followersFile = req.files.find(file => (file.originalname === 'followers_1.html') || (file.originalname === 'followers.html'));
const followingFile = req.files.find(file => file.originalname === 'following.html');
if (!followersFile || !followingFile) {
return res.status(500).send('Required files are missing.');
}
await deleteColumns(req.user)
// Promise that is used to inject user information information into sql database
await new Promise( async (resolve, reject) => {
try {
const data = followersFile.buffer.toString("utf-8")
const dom = new JSDOM(data);
const links = dom.window.document.querySelectorAll("a");
for (let i = 0; i < links.length; i++) {
await insertFollowers(links[i].textContent,links[i].href, req.user)
await insertFollowers(links[i].textContent, req.user)
}
resolve()
}
catch (err) {
reject(err)
}
})
// Promise that is used to inject user information information into sql database
await new Promise( async (resolve, reject) => {
try {
const data = followingFile.buffer.toString("utf-8")
const dom = new JSDOM(data);
const links = dom.window.document.querySelectorAll("a");
for (let i = 0; i < links.length; i++) {
await insertFollowing(links[i].textContent, links[i].href, req.user)
await insertFollowing(links[i].textContent, req.user)
}
resolve()
}
Expand All @@ -165,45 +130,36 @@ app.post('/uploads', authenticateToken, uploads.single('zipfile'), async (req, r
// const result = await parse()
res.json(result)
}
// Various error handling
catch(err) {
console.log(err)
return res.status(500).send('Upload error');
}}, (err, req, res, next) => {
if (err instanceof multer.MulterError) {
console.log(err)
// A Multer error occurred when uploading.
return res.status(500).send('An error occurred during the file upload.');
return res.status(500).send('An error occurred during the file upload.'); // Send a generic error message
} else if (err) {
// An unknown error occurred when uploading.
return res.status(500).send('An error occurred during the request.');
return res.status(500).send('An error occurred during the request.'); // Send a generic error message
} else {
// Handle other errors
return res.status(500).json({ error: 'Internal server error' });
}
}
}// res.json({status: 'form data recieved' })
)
// Handles user logout
app.post('/logout', (req, res) => {
res.clearCookie('auth_token', {
httpOnly: true, // Ensures the cookie is not accessible via JavaScript
secure: isProduction, // Ensures the cookie is only sent over HTTPS
sameSite: 'None', // Ensures the cookie is sent with cross-site requests
domain: '.instagram-tool.duckdns.org', // Make sure the domain has a dot in front
path: '/', // Ensure this matches the path where the cookie was set
});
<<<<<<< Updated upstream
res.clearCookie('auth_token'); // Clear the auth_token cookie
=======
res.clearCookie('auth_token', logoutCookieOptions);
>>>>>>> Stashed changes
res.json({ message: 'Logout successful' });
});
// Handles user registration
// Stores information in database table for easy and secure lookup
app.post('/register', uploads.none(), async (req, res) => {
try {
const { username, password } = req.body;
const result = await pool.query('SELECT username FROM users WHERE username = ?', [username])
if (result[0].length > 0) {
return res.status(400).send('Username already taken')
}
// Use bcrypt in order to store hashed version of password for increased security
const hashedPassword = await bcrypt.hash(password, 10)
await pool.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword])
res.json('User registered successfully')
Expand All @@ -212,7 +168,6 @@ app.post('/register', uploads.none(), async (req, res) => {
return res.status(500).send('Database error');
}
})
// Checks if user is authenticated
app.get('/protected', authenticateToken, (req, res) => {
res.sendStatus(200);
});
Expand All @@ -221,50 +176,71 @@ app.post('/login', uploads.none(), async (req, res) => {
const username = req.body.username;
const password = req.body.password;
const remember = req.body.remember;

// Find the user by username
const result = await pool.query('SELECT * FROM users WHERE username = ?', [username]);
if (result[0].length === 0) {
return res.status(400).send('User not found');
}

// Compare passwords
const user = result[0];
const isMatch = await bcrypt.compare(password, user[0].password);
if (!isMatch) {
return res.status(400).send('Invalid password')
}
const accessToken = jwt.sign(username, process.env.ACCESS_TOKEN_SECRET)
// Sets expiration to 1 week and one hour respectively
const maxAges = String(remember) == "true" ? 604800000 : 3600000;
<<<<<<< Updated upstream
res.cookie('auth_token', accessToken, {
httpOnly: true, // Prevents JavaScript from accessing the cookie (helps prevent XSS) // Cookie is only sent over HTTPS (use `false` during local development)
sameSite: 'Strict', // Prevents the cookie from being sent with cross-site requests (helps prevent CSRF)
maxAge: maxAges // Sets cookie expiration time
})
res.json( {message: "login sucess"})
=======
res.cookie('auth_token', accessToken, isProduction ? {
httpOnly: true,
sameSite: 'None',
maxAge: maxAges, // Example maxAge
domain: 'instagram-tool.duckdns.org',
secure: isProduction, // Set secure cookies only in production
} : {
httpOnly: true,
sameSite: 'Strict',
maxAge: maxAges, // Example maxAge
secure: isProduction, // Set secure cookies only in production
});
res.json( {message: "login success"})
>>>>>>> Stashed changes
} catch (err) {
console.error('Error during login:', err); // Log the error for debugging
return res.status(500).send('Database error');
}
});
// Inserts user information into database
function insertFollowing(name, user, link){
app.listen(port, () => console.log(`server has started on port: ${port}`))

function insertFollowing(name, user){
return pool.query(`
INSERT INTO following (user_id, following_link, following_id)
VALUES (?, ?, ?)
`, [link, user, name])
INSERT INTO following (user_id, following_id)
VALUES (?, ?)
`, [user, name])
}
// Parses user information from database and stores in dictionary to be returned to client browser

function parse(userId){
return pool.query(`SELECT following_id, following_link
return pool.query(`SELECT following_id
FROM following
WHERE user_id = ? AND following_id NOT IN (SELECT follower_id FROM followers WHERE user_id = ?);
`, [userId, userId])
}
// Authentices user information

function authenticateToken(req, res, next) {
const token = req.cookies.auth_token
console.log(req.cookies.auth_token)
<<<<<<< Updated upstream
// const authHeader = req.headers['authorization']
// const token = authHeader && authHeader.split(' ')[1]
=======
>>>>>>> Stashed changes
if (token == null) {
return res.sendStatus(401)
}
Expand All @@ -276,7 +252,6 @@ function authenticateToken(req, res, next) {
next()
})
}
// Deletes user information upon new upload in order to save space
function deleteColumns(user) {
return pool.query(`
DELETE FROM following WHERE user_id = ?
Expand All @@ -287,25 +262,9 @@ function deleteColumns(user) {
`, [user]);
});
}
// Inserts user information into database
function insertFollowers(name, link, user){
function insertFollowers(name, user){
return pool.query(`
INSERT INTO followers (user_id, follower_link, follower_id)
VALUES (?, ?, ?)
`, [user, link, name])
}

if (isProduction) {
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/instagram-tool.duckdns.org/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/instagram-tool.duckdns.org/fullchain.pem')
};

https.createServer(options, app).listen(port, '0.0.0.0', () => {
console.log('Server is running on https://yourdomain.duckdns.org:8383');
});
} else {
app.listen(port, 'localhost', () => {
console.log('Server is running on http://localhost:8383');
});
}
INSERT INTO followers (user_id, follower_id)
VALUES (?, ?)
`, [user, name])
}

0 comments on commit ae1a2cf

Please sign in to comment.