diff options
-rw-r--r-- | .eslintignore | 3 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .vscode/settings.json | 5 | ||||
-rw-r--r-- | package.json | 4 | ||||
-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 | ||||
-rw-r--r-- | yarn.lock | 41 |
16 files changed, 184 insertions, 202 deletions
diff --git a/.eslintignore b/.eslintignore index 7773828..8a4274e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -dist/
\ No newline at end of file +dist/ +src/public/lib/ @@ -122,6 +122,8 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +.dccache + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/.vscode/settings.json b/.vscode/settings.json index a3606ef..3f638b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,8 @@ "**/**.js": { "when": "$(basename).tsx" } - } + }, + "cSpell.words": [ + "fileupload" + ] }
\ No newline at end of file diff --git a/package.json b/package.json index 45ff92a..e0a656c 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "csurf": "^1.11.0", "ejs": "^3.1.6", "express": "^4.17.2", + "express-fileupload": "^1.3.1", "express-rate-limit": "^6.2.1", "express-slow-down": "^1.4.0", "helmet": "^5.0.2" @@ -12,6 +13,7 @@ "@types/cookie-parser": "^1.4.2", "@types/csurf": "^1.11.2", "@types/express": "^4.17.13", + "@types/express-fileupload": "^1.2.2", "@types/express-slow-down": "^1.3.2", "@types/node": "^17.0.17", "@typescript-eslint/eslint-plugin": "^5.11.0", @@ -43,4 +45,4 @@ "repository": "https: //github.com/RosstheRoss/4951w-pendulum", "license": "MIT", "private": true -}
\ No newline at end of file +} 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 @@ -72,6 +72,13 @@ "@types/connect" "*" "@types/node" "*" +"@types/busboy@^0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-0.3.2.tgz#2f29b017513415399c42632ae6a7cfcb1409b79c" + integrity sha512-iEvdm9Z9KdSs/ozuh1Z7ZsXrOl8F4M/CLMXPZHr3QuJ4d6Bjn+HBMC5EMKpwpAo8oi8iK9GZfFoHaIMrrZgwVw== + dependencies: + "@types/node" "*" + "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -93,6 +100,14 @@ dependencies: "@types/express-serve-static-core" "*" +"@types/express-fileupload@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/express-fileupload/-/express-fileupload-1.2.2.tgz#98c10e900c222744bba16c848505a1fa95ab3ff0" + integrity sha512-sWU1EVFfLsdAginKVrkwTRbRPnbn7dawxEFEBgaRDcpNFCUuksZtASaAKEhqwEIg6fSdeTyI6dIUGl3thhrypg== + dependencies: + "@types/busboy" "^0" + "@types/express" "*" + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -399,6 +414,13 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +busboy@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" + integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== + dependencies: + dicer "0.3.0" + bytes@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" @@ -681,6 +703,13 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +dicer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" + integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== + dependencies: + streamsearch "0.1.2" + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -967,6 +996,13 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +express-fileupload@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.3.1.tgz#3238472def305b8cb4cc5936a953761d0c442011" + integrity sha512-LD1yabD3exmWIFujKGDnT1rmxSomaqQSlUvzIsrA1ZgwCJ6ci7lg2YHFGM3Q6DfK+Yk0gAVU7GWLE7qDMwZLkw== + dependencies: + busboy "^0.3.1" + express-rate-limit@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.2.1.tgz#4a7619634fb24417ae723ad2ac3707b38e2e1c64" @@ -2312,6 +2348,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" |