initial commit
This commit is contained in:
15 files changed
+401
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
MONGODB_HOST=YourMongoDBClusterHere
|
||||||
|
MONGODB_USER=YourUsernameHere
|
||||||
|
MONGODB_PASSWORD=YourPasswordHere
|
||||||
|
MONGODB_DATABASE=YourMongoDBDatabaseHere
|
||||||
|
MONGODB_SESSION_SECRET=Some_Session_Key_Here1
|
||||||
|
NODE_SESSION_SECRET=Some_Session_Key_Here2
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
.env
|
||||||
|
*.sync-confcict-*
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mongo_user: process.env.MONGODB_USER,
|
||||||
|
mongo_password: process.env.MONGODB_PASSWORD,
|
||||||
|
mongo_host: process.env.MONGODB_HOST,
|
||||||
|
mongo_database: process.env.MONGODB_DATABASE,
|
||||||
|
mongo_secret: process.env.MONGODB_SESSION_SECRET,
|
||||||
|
express_secret: process.env.NODE_SESSION_SECRET,
|
||||||
|
};
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const path = require("path");
|
||||||
|
const session = require("express-session");
|
||||||
|
const MongoStore = require("connect-mongo");
|
||||||
|
const MongoClient = require("mongodb").MongoClient;
|
||||||
|
const joi = require("joi");
|
||||||
|
const bcrypt = require("bcrypt");
|
||||||
|
|
||||||
|
// Initialize app
|
||||||
|
const app = express();
|
||||||
|
const port = 8000;
|
||||||
|
|
||||||
|
// Connect MongoDB
|
||||||
|
const config = require("./config/config");
|
||||||
|
|
||||||
|
const mongoURI = `mongodb+srv://${config.mongo_user}:${config.mongo_password}@${config.mongo_host}`;
|
||||||
|
let users;
|
||||||
|
async function connectMongo() {
|
||||||
|
try {
|
||||||
|
const connection = await MongoClient.connect(mongoURI, { connectTimeoutMS: 1000 });
|
||||||
|
users = connection.db(config.mongo_database).collection("users");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to connect to mongodb at (mongodb+srv://${config.mongo_host})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectMongo();
|
||||||
|
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use('/static', express.static(path.join(__dirname, "./public")));
|
||||||
|
|
||||||
|
// Sessions
|
||||||
|
app.use(session({
|
||||||
|
secret: config.express_secret,
|
||||||
|
store: MongoStore.create({ mongoUrl: `${mongoURI}/${config.mongo_database}`, crypto: { secret: config.mongo_secret } }),
|
||||||
|
resave: true,
|
||||||
|
saveUninitialized: false,
|
||||||
|
cookie: { maxAge: 60000 },
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**** Page routes ****/
|
||||||
|
|
||||||
|
app.get("/", async (req, res) => {
|
||||||
|
res.set('Content-Type', 'text/html');
|
||||||
|
|
||||||
|
res.render("index", { authenticated: req.session.authenticated, username: req.session.username });
|
||||||
|
return res.status(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/login", (req, res) => {
|
||||||
|
res.set('Content-Type', 'text/html');
|
||||||
|
res.render("login", { message: req.session.message });
|
||||||
|
return res.status(200);
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get("/signup", (req, res) => {
|
||||||
|
res.set('Content-Type', 'text/html');
|
||||||
|
res.render("signup", { message: req.session.message });
|
||||||
|
return res.status(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/members", (req, res) => {
|
||||||
|
if (!req.session.authenticated) {
|
||||||
|
req.session.message = "Please login";
|
||||||
|
res.status(401);
|
||||||
|
return res.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
let names = ["carl", "gary", "jebediah"];
|
||||||
|
let name = names[Math.floor(Math.random() * names.length)];
|
||||||
|
|
||||||
|
res.set('Content-Type', 'text/html');
|
||||||
|
res.render("members", { name: name, username: req.session.username });
|
||||||
|
return res.status(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*** Authentication routes ***/
|
||||||
|
|
||||||
|
app.post("/login", async (req, res) => {
|
||||||
|
const schema = joi.string().email().required();
|
||||||
|
let valid = schema.validate(req.body.email);
|
||||||
|
|
||||||
|
if (valid.error) {
|
||||||
|
req.session.message = "Invalid input";
|
||||||
|
res.status(400);
|
||||||
|
return res.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = await users.findOne({ email: req.body.email });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
req.session.message = "User not found";
|
||||||
|
res.status(404);
|
||||||
|
return res.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await bcrypt.compare(req.body.password, user.password)) {
|
||||||
|
req.session.authenticated = true;
|
||||||
|
req.session.username = user.name;
|
||||||
|
req.session.message = "";
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
return res.redirect('/members');
|
||||||
|
} else {
|
||||||
|
req.session.message = "Incorrect password";
|
||||||
|
res.status(401);
|
||||||
|
return res.redirect("/login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/signup", async (req, res) => {
|
||||||
|
const schema = joi.object({
|
||||||
|
name: joi.string().alphanum().max(20).required(),
|
||||||
|
email: joi.string().email().required(),
|
||||||
|
password: joi.string().max(20).required()
|
||||||
|
});
|
||||||
|
|
||||||
|
let valid = schema.validate(req.body);
|
||||||
|
|
||||||
|
if (valid.error) {
|
||||||
|
req.session.message = "Invalid input";
|
||||||
|
res.status(400);
|
||||||
|
return res.redirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = await bcrypt.hash(req.body.password, 12);
|
||||||
|
await users.insertOne({
|
||||||
|
name: req.body.name,
|
||||||
|
email: req.body.email,
|
||||||
|
password: password,
|
||||||
|
});
|
||||||
|
|
||||||
|
req.session.message = "Please login";
|
||||||
|
res.status(200);
|
||||||
|
return res.redirect("/login");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/logout", (req, res) => {
|
||||||
|
req.session.destroy();
|
||||||
|
res.status(200);
|
||||||
|
return res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*** 404 Not found ***/
|
||||||
|
|
||||||
|
app.get("/*splat", (req, res) => {
|
||||||
|
res.set('Content-Type', 'text/html');
|
||||||
|
res.render("notFound");
|
||||||
|
return res.status(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server listing on port ${port}`);
|
||||||
|
});
|
||||||
Executable
+33
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "assignment1",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"dev": "nodemon index.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/SowinskiBraeden/comp2537-assignment1.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/SowinskiBraeden/comp2537-assignment1/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/SowinskiBraeden/comp2537-assignment1#readme",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"connect-mongo": "^5.1.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
|
"joi": "^17.13.3",
|
||||||
|
"mongodb": "^6.16.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Oswald&display=swap');
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0pt;
|
||||||
|
padding: 0pt;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'Oswald', sans-serif;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center p {
|
||||||
|
margin: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center a {
|
||||||
|
padding-left: 6pt;
|
||||||
|
padding-right: 6pt;
|
||||||
|
margin-top: 2pt;
|
||||||
|
margin-bottom: 2pt;
|
||||||
|
font-size: 24px;
|
||||||
|
background-color: lightsteelblue;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-msg {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 923 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 334 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
Self-graded Assignment 1 Checklist
|
||||||
|
|
||||||
|
Criteria
|
||||||
|
========
|
||||||
|
Student Name: Braeden Sowinski
|
||||||
|
Student Set: 1D
|
||||||
|
|
||||||
|
[x] A home page links to signup and login, if not logged in; and links to members and signout, if logged in.
|
||||||
|
[x] A members page that displays 1 of 3 random images stored on the server.
|
||||||
|
[x] The members page will redirect to the home page if no valid session is found.
|
||||||
|
[x] The signout buttons end the session.
|
||||||
|
[x] All secrets, encryption keys, database passwords are stored in a .env file.
|
||||||
|
|
||||||
|
[x] The .env file is NOT in your git repo.
|
||||||
|
[x] Password is BCrypted in the MongoDB database.
|
||||||
|
[x] Your site is hosted in a hosting service like Qoddi.
|
||||||
|
[x] A 404 page that "catches" all invalid page hits and that sets the status code to 404.
|
||||||
|
[x] Session information is stored in an encrypted MongoDB session database. Sessions expire after 1 hour.
|
||||||
|
|
||||||
|
50/50 (Total grade out of 50, 5 marks each x 10 items)
|
||||||
|
|
||||||
|
*Note items are considered *fully* complete (marked with an x inside the box: [x]), OR incomplete (box is left empty: [ ])
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>2537 Assignment 1</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="center">
|
||||||
|
<% if (authenticated) { %>
|
||||||
|
<h1>Welcome <%= username %></h1>
|
||||||
|
<a href="/members">Go to members page</a>
|
||||||
|
<a style="background-color: grey; margin-top: 12pt;" href="/logout">Logout</a>
|
||||||
|
<% } else { %>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<p>or</p>
|
||||||
|
<a href="/signup">Signup</a>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>2537 Assignment 1</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="center">
|
||||||
|
<form action="/login" method="POST">
|
||||||
|
<label for="username">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
</label>
|
||||||
|
<input type="email" name="email" placeholder="Email" id="email" required>
|
||||||
|
|
||||||
|
<label for="password">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</label>
|
||||||
|
<input type="password" name="password" placeholder="Password" id="password" required>
|
||||||
|
|
||||||
|
<input id="login" type="submit" value="Login">
|
||||||
|
<h3 id="flash-msg" class="flash-msg"><%= message %></h3>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/scripts/loadFlash.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>2537 Assignment 1</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
</head>
|
||||||
|
<body style="padding: 12pt;">
|
||||||
|
<h3>Hello <%= username %></h3>
|
||||||
|
<figure>
|
||||||
|
<img style="width: 400pt;" src="/static/images/<%= name %>.png">
|
||||||
|
<figcaption><%= name %></figcaption>
|
||||||
|
</figure>
|
||||||
|
<br>
|
||||||
|
<a style="background-color: grey; padding: 6px; margin-top: 12pt;" href="/logout">Logout</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>2537 Assignment 1</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>404 - Not found</h1>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>2537 Assignment 1</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/login.css">
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="background"></div>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<form action="/signup" method="POST">
|
||||||
|
<label for="name">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
</label>
|
||||||
|
<input type="text" name="name" placeholder="Peter Venkman" id="name" required>
|
||||||
|
|
||||||
|
<label for="email">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
</label>
|
||||||
|
<input type="email" name="email" placeholder="peter_v@ghostbusters.com" id="email" required>
|
||||||
|
|
||||||
|
<label for="password">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</label>
|
||||||
|
<input type="password" name="password" placeholder="Password" id="password" required>
|
||||||
|
|
||||||
|
<input id="login" type="submit" value="Sign Up">
|
||||||
|
<h3 id="flash-msg" class="flash-msg"><%= message %></h3>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/scripts/loadFlash.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user