diff options
Diffstat (limited to '')
-rw-r--r-- | src/index.ts | 56 | ||||
-rw-r--r-- | src/public/css/style.css | 101 | ||||
-rw-r--r-- | src/public/js/form.js | 17 | ||||
-rw-r--r-- | src/routes/api.ts | 40 | ||||
-rw-r--r-- | src/routes/api/actuate.ts | 0 | ||||
-rw-r--r-- | src/routes/api/login.ts | 0 | ||||
-rw-r--r-- | src/views/pages/about.ejs | 6 | ||||
-rw-r--r-- | src/views/pages/index.ejs | 76 | ||||
-rw-r--r-- | src/views/pages/login.ejs | 11 | ||||
-rw-r--r-- | src/views/partials/head.ejs | 10 | ||||
-rw-r--r-- | src/views/partials/nav.ejs | 14 |
11 files changed, 132 insertions, 199 deletions
diff --git a/src/index.ts b/src/index.ts index 9e7d082..bd2c7d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,56 +1,54 @@ import express, { Request, Response } from 'express'; -import rateLimit from 'express-rate-limit'; -import slowDown from 'express-slow-down'; + import path from 'path'; import { env } from 'process'; import helmet from 'helmet'; import csurf from 'csurf'; import cookieParser from 'cookie-parser'; +import rateLimit from 'express-rate-limit'; +import api from './routes/api'; const app = express(); -// Middleware -const port: string = env.PORT || '2000'; +/* MIDDLEWARE */ -app.use(cookieParser()); -const csrf = csurf({ cookie: true }); +// Rate limiting const rateLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute - max: 30, // Limit each IP to 100 requests per `window` (here, per 15 minutes) + max: 40, // Limit each IP to 100 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); -const speedLimiter = slowDown({ - windowMs: 15 * 60 * 1000, // 15 minutes - delayAfter: 100, // allow 100 requests per 15 minutes, then... - delayMs: 500 // begin adding 500ms of delay per request above 100: - // request # 101 is delayed by 500ms - // request # 102 is delayed by 1000ms - // request # 103 is delayed by 1500ms - // etc. -}); -// This will be run behind an nginx proxy -app.enable('trust proxy'); -// apply to all requests -app.use(speedLimiter); -app.use('/api', rateLimiter); +app.use(rateLimiter); + +// CSRF protection +app.use(cookieParser()); +const csrf = csurf({ cookie: true }); + + +// Hide the software being used (helps security) app.use(helmet()); -// Add ejs as view engine -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views/pages')); -app.use('/public', express.static(path.join(__dirname, 'public'))); +// The API +app.use('/api', api); + +/* RENDERING */ + +app.set('view engine', 'ejs'); // Add ejs as view engine +app.set('views', path.join(__dirname, 'views/pages')); // Set views directory (where the ejs lies) +app.use('/public', express.static(path.join(__dirname, 'public'))); // Set static directory (where the static CSS/JS/images lie) + +/* ROUTING */ app.get('/', csrf, (req: Request, res: Response) => { - res.render('index', { - errors: [], - }); + res.render('index', { csrfToken: req.csrfToken() }); }); - app.get('/about', csrf, (req: Request, res: Response) => { res.render('about'); }); +// Start the server +const port = env.PORT || 2000; app.listen(port, () => { console.log(`Server is listening on port ${port}`); });
\ No newline at end of file diff --git a/src/public/css/style.css b/src/public/css/style.css index cdcad8c..0e7892d 100644 --- a/src/public/css/style.css +++ b/src/public/css/style.css @@ -1,12 +1,13 @@ body { background-color: black; -} - -.header { color: white; text-align: center; vertical-align: middle; position: relative; +} + +.header { + /* top: 30px; */ margin: 5px; font-family: "Times New Roman", Times, serif; @@ -24,17 +25,12 @@ p { } -/* Customize the positioning of the logo */ - - - - /*-------------------------Buttons-----------------------------------------------------*/ /* Used for id=button1 */ -#button1 { +#actuate_but { background-color: #0a3f73; border: solid; border-color: #d5d6d2; @@ -51,68 +47,13 @@ p { /* Used for id=button1 - What the button looks like when a user puts one's cursor on it */ -#button1:hover { +#actuate_but:hover { background-color: white; color: maroon; cursor: pointer; } -/* Used for id=button2 */ - -#button2 { - /* #ebcba9 is similar to a tan color */ - background-color: #ebcba9; - border: solid; - border-color: #d5d6d2; - /* #693100 is similar to a brown color */ - color: #693100; - padding: 10px 20px; - text-align: center; - text-decoration: none; - font-family: "Constantia", sans-serif; - border-radius: 10px; - display: inline-block; - font-size: 20px; -} - - -/* Used for id=button2 - What the button looks like when a user puts one's cursor on it */ - -#button2:hover { - background-color: white; - color: #0a3f73; - cursor: pointer; -} - - -/* Used for id=button2 */ - -#button3 { - /* #cf7d4e is similar to a light brown color */ - background-color: #cf7d4e; - border: solid; - border-color: #d5d6d2; - /* #094f02 is similar to a dark green color */ - color: #094f02; - padding: 10px 20px; - text-align: center; - text-decoration: none; - font-family: "Constantia", sans-serif; - border-radius: 10px; - display: inline-block; - font-size: 20px; -} - - -/* Used for id=button2 - What the button looks like when a user puts one's cursor on it */ - -#button3:hover { - background-color: white; - color: maroon; - cursor: pointer; -} - a { color: white; text-decoration: none; @@ -134,12 +75,6 @@ a { } -/* Used for id=title1 */ - -#title1 { - font-size: xx-large; -} - /* Used for id=title2 */ @@ -158,14 +93,6 @@ a { margin-top: 15px; } -#subtitle { - font-size: large; -} - -#subtitle1 { - font-size: 23px; -} - #Background { color: #ffbb00; } @@ -175,14 +102,6 @@ a { color: #4294cf; } -#choose_file { - border: none; - text-decoration: none; - display: inline-block; -} - -#submit {} - /* Used for id=Start */ @@ -203,11 +122,11 @@ a { background-color: #7a0019; } -li { +.navbar li { float: left; } -li a { +.navbar li a { display: block; color: white; text-align: center; @@ -216,9 +135,7 @@ li a { text-decoration: none; } - - -li a:hover { +.navbar li a:hover { background-color: #ffcc33; color: #000000; /* Remove underlines from links */ diff --git a/src/public/js/form.js b/src/public/js/form.js new file mode 100644 index 0000000..cfa80fd --- /dev/null +++ b/src/public/js/form.js @@ -0,0 +1,17 @@ +document.getElementById('upload').onsubmit = function () { + var data = new FormData(document.getElementById('upload')); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/upload'); + xhr.send(data); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status == 200) { + var response = JSON.parse(xhr.responseText); + if (response.success) { + document.getElementById('success').style.display = 'block'; + } else { + document.getElementById('error').style.display = 'block'; + } + } + }; + return false; +}; diff --git a/src/routes/api.ts b/src/routes/api.ts new file mode 100644 index 0000000..4612c16 --- /dev/null +++ b/src/routes/api.ts @@ -0,0 +1,40 @@ +import express, { Request, Response } from 'express'; +import csurf from 'csurf'; +import cookieParser from 'cookie-parser'; +import fileUpload, { UploadedFile } from 'express-fileupload'; +import slowDown from 'express-slow-down'; + + +// Slow down everything to prevent DoS attacks +const speedLimiter = slowDown({ + windowMs: 5 * 60 * 1000, // 15 minutes + delayAfter: 50, // allow 100 requests per 5 minutes, then... + delayMs: 500 // begin adding 500ms of delay per request above 100: + // request # 101 is delayed by 500ms + // request # 102 is delayed by 1000ms + // request # 103 is delayed by 1500ms + // etc. +}); + + +const api = express.Router(); + +api.use(fileUpload()); +api.use(speedLimiter); + +// CSRF protection +api.use(cookieParser()); +const csrf = csurf({ cookie: true }); + +api.post('/upload', csrf, (req: Request, res: Response) => { + if (!req.files || Object.keys(req.files).length === 0) + return res.status(400).json({ err: 'ENOENT' }); + // Kludge to prevent a compiler error + const file: UploadedFile = req.files.file as UploadedFile; + console.log(file.mimetype); + if (file.mimetype !== 'text/x-python') + return res.status(400).json({ err: 'EINVAL' }); + res.status(200).json({ err: null }); +}); + +export default api;
\ No newline at end of file diff --git a/src/routes/api/actuate.ts b/src/routes/api/actuate.ts deleted file mode 100644 index e69de29..0000000 --- a/src/routes/api/actuate.ts +++ /dev/null diff --git a/src/routes/api/login.ts b/src/routes/api/login.ts deleted file mode 100644 index e69de29..0000000 --- a/src/routes/api/login.ts +++ /dev/null diff --git a/src/views/pages/about.ejs b/src/views/pages/about.ejs index 3b376d5..022354c 100644 --- a/src/views/pages/about.ejs +++ b/src/views/pages/about.ejs @@ -12,19 +12,19 @@ <title>About the Remotely Accessible Inverted Pendulum</title> <div class="header"> <img src="public/img/site_logo.png" alt="Pendulum logo" width="160" height="130" /> - <h1 id="title1">Remotely Accessible Inverted Pendulum</h1> + <h1>Remotely Accessible Inverted Pendulum</h1> </div> <div id="main"> <div id="about"> - <h2 id="subtitle"> + <h3 id="subtitle"> Created by <br /> Abrar Nair Islam, Brendan Lake, Cory Ohnsted, Matt Strapp, Kiflu Woldegiorgis (2022) <br /> Sam Hansen, Rezkath Awal, Donovan Peterson, Joseph Jewett (2021) <br /> Alin Butoi, Paul D’Amico, Dat Nguyen, Ross Olson, Rachel Schow (2019) <br /> <br /> Advisor: Professor Andrew Lamperski <br /> Sponsored by the University of Minnesota -Twin Cities <br /> - </h2> + </h3> <br /> <h1 id="Background">Background</h1> <p> diff --git a/src/views/pages/index.ejs b/src/views/pages/index.ejs index 6087732..0e6fe15 100644 --- a/src/views/pages/index.ejs +++ b/src/views/pages/index.ejs @@ -4,73 +4,23 @@ <head> <!-- HTML headers information --> <%- include('../partials/head.ejs') %> - <script type="text/javascript" src="/static/js/app.js"></script> + <script type="module" src="public/js/form.js" defer></script> </head> <body> + <!-- Get the navbar --> <%- include('../partials/nav.ejs') %> - <div class="header"> - <!-- Get the navbar --> - - <div class="row"> - <div class="col"> - - <h1 id="title2">Homepage</h1> - <hr> - - - <h3 id="subtitle1">Please upload a Python file (.py file extension) onto the Inverted Pendulum</h3> - <!--**************************Send form data to web server**************************--> - <form method="POST" action="/index" enctype="multipart/form-data"> - <p><input type="file" name="file" accept=".py" /></p> - <p><input type="submit" value="Upload File" onclick="update()"></p> - </form> - - For file upload progress bar - <div id="myProgress"> - <div id="myBar"></div> - </div> - - <!--The following lines are for the warning flash() messages--> - <% if (errors) { %> - <% for (const message in errors) { %> - <div class="alert alert-warning alert-dismissible fade show" role="alert"> - <span> - <% message %> - </span> - <button type="button" class="close" data-dismiss="alert" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <% } %> - <% } %> - - <!--The following lines are for the danger flash() messages--> - <% if (errors) { %> - <% for (const message in errors) { %> - <div class="alert alert-danger alert-dismissible fade show" role="alert"> - <span> - <% message %> - </span> - <button type="button" class="close" data-dismiss="alert" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <% } %> - <% } %> - - - <div class="mb-3"> - <div class="form-group"> - <label id="Start">Start pendulum: </label> - <!-- <button class="btn btn-primary" id="onbutton">Actuate!</button> --> - <button type="button" id=button1 onclick="window.location.href='{{ url_for( 'run_test_file') }}';">Actuate!</button> - </div> - </div> - <hr> - </div> - </div> - </div> + <br /> + <h2>Please upload a Python file (.py file extension) onto the Inverted Pendulum.</h2> + <br /> <br /> + <form id="upload" enctype="multipart/form-data"> + <input type="hidden" name="_csrf" value="<%= csrfToken %>"> + <input type="file" name="file" accept=".py" /> + <br /> <br /> + <br /> <br /> + <label id="Start">Start pendulum: </label> + <input type="submit" id="actuate_but" value="Actuate!" /> + </form> </body> </html>
\ No newline at end of file diff --git a/src/views/pages/login.ejs b/src/views/pages/login.ejs index e69de29..7e77ee2 100644 --- a/src/views/pages/login.ejs +++ b/src/views/pages/login.ejs @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <!-- HTML headers information --> + <%- include('../partials/head.ejs') %> +</head> + +<body> + <%- include('../partials/nav.ejs') %> +</body>
\ No newline at end of file diff --git a/src/views/partials/head.ejs b/src/views/partials/head.ejs index 02786be..320f0e6 100644 --- a/src/views/partials/head.ejs +++ b/src/views/partials/head.ejs @@ -1,6 +1,6 @@ -<meta charset="utf-8" /> -<meta http-equiv="X-UA-Compatible" content="IE=edge" /> -<meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<link rel="stylesheet" href="public/css/style.css" type="text/css"> -<link rel="icon" href="public/img/site_logo.png" type="image/x-icon">
\ No newline at end of file + <link rel="stylesheet" href="public/css/style.css" type="text/css"> + <link rel="icon" href="public/img/site_logo.png" type="image/x-icon">
\ No newline at end of file diff --git a/src/views/partials/nav.ejs b/src/views/partials/nav.ejs index ab2626b..1ee6c00 100644 --- a/src/views/partials/nav.ejs +++ b/src/views/partials/nav.ejs @@ -1,7 +1,7 @@ -<nav> - <ul class="navbar"> - <li><a href="/">Home</a></li> - <li><a href="/about">About the Inverted Pendulum</a></li> - <li><a href="https://github.com/RosstheRoss/4951w-pendulum">GitHub Repo</a></li> - </ul> -</nav>
\ No newline at end of file + <nav> + <ul class="navbar"> + <li><a href="/">Home</a></li> + <li><a href="/about">About the Inverted Pendulum</a></li> + <li><a href="https://github.com/RosstheRoss/4951w-pendulum">GitHub Repo</a></li> + </ul> + </nav>
\ No newline at end of file |