From 5366d8737f0c589f575dde1fa7207f295e4c6f40 Mon Sep 17 00:00:00 2001
From: William Moody <e11736947@student.tuwien.ac.at>
Date: Sun, 21 Mar 2021 17:43:02 +0100
Subject: [PATCH] Pretty much done

---
 Dockerfile                |   1 +
 app.js                    | 222 +++++++++++++++++++++++++++++++++++---
 package-lock.json         | 154 +++++++++++++++++++++++++-
 package.json              |   6 +-
 public/assets/chat.svg    |  83 ++++++++++++++
 public/assets/styles.css  |  21 ++++
 views/pages/index.ejs     |  21 ++++
 views/pages/register.ejs  |  51 +++++++++
 views/partials/footer.ejs |   4 +-
 views/partials/head.ejs   |   3 +-
 views/partials/header.ejs |  43 +++++---
 11 files changed, 579 insertions(+), 30 deletions(-)
 create mode 100644 Dockerfile
 create mode 100644 public/assets/chat.svg
 create mode 100644 views/pages/register.ejs

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..9d1491c
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1 @@
+FROM alpine:latest
\ No newline at end of file
diff --git a/app.js b/app.js
index 418007a..bf7f01c 100644
--- a/app.js
+++ b/app.js
@@ -1,36 +1,230 @@
-const MongoClient = require('mongodb').MongoClient;
-const db_url = "mongodb://localhost:27017/";
+// Chat.JS
+// William Moody
+// 21.03.2021
 
-const express = require('express');
-const app = new express();
-const port = 3000;
+// Database connection 
+var MongoClient = require('mongodb').MongoClient;
+var db_url = "mongodb://localhost:27017/";
 
+// Necessary packages for authentication
+var session = require('express-session');
+var bodyParser = require('body-parser');
+var crypto = require('crypto');
+
+// Necessary packages for drafts
+var cookieParser = require('cookie-parser');
+var serialize = require('node-serialize');
+
+// Web-App framework
+var express = require('express');
+var app = new express();
+var port = 3000;
+
+// Set some Express.JS settings
 app.set('view engine', 'ejs');
 app.use('/public', express.static('public'));
+app.use(session({
+    secret: 'c02b7d24a066adb747fdeb12deb21bfa',
+    resave: true,
+    saveUninitialized: true
+}));
+app.use(bodyParser.urlencoded({extended: true}));
+app.use(bodyParser.json());
+app.use(cookieParser());
 
+/**
+ * Home page
+ * 
+ * Unauthenticated users may read chat
+ * Authenticated users may post messages
+ * 
+ * Limits messages retrieved to 100 to keep loading times short
+ */
 app.get('/', function(req, res) {
-    console.log('[*] GET /');
+    console.log('[*] ' + req.ip + ' > GET /');
+    var draft = null;
+    if (req.cookies.draft) {
+        draft = serialize.unserialize(new Buffer(req.cookies.draft, 'base64').toString()).msg;
+        console.log(draft);
+    }
     MongoClient.connect(db_url, { useNewUrlParser:true, useUnifiedTopology:true }, function(err, db) {
-        if (err) throw err;
+        if (err) {
+            throw err;
+        }
         var dbo = db.db("chatjs");
         dbo.collection("messages").aggregate([
-            { 
-                $lookup: 
-                {
+            {
+                $lookup:  {
                     from: 'users', 
                     localField: 'author', 
                     foreignField: '_id', 
                     as: 'author'
                 }
+            },
+            {
+                $sort: { 
+                    datetime: -1 
+                }
+            },
+            {
+                $limit: 100
             }
         ]).toArray(function(err, result) {
-            if (err) throw err;
-            res.render('pages/index', {messages: result});
+            if (err) {
+                throw err;
+            }
+            res.render('pages/index', {messages: result, session: req.session, draft:draft});
             db.close();
         });
     });
 });
 
-const server = app.listen(port, function() {
-    console.log('[+] Chat.JS is running on :' + port);
+/**
+ * Register page
+ * 
+ * Authenticated users can't see this
+ */
+app.get('/register', function(req, res) {
+    console.log('[*] ' + req.ip + ' > GET /register');
+    if (req.session.logged_in == true) {
+        res.redirect('/');
+    }
+    else {
+        res.render('pages/register', {session: req.session, error:null});
+    }
+});
+
+/**
+ * Register API
+ * 
+ * Requests from Authenticated users are ignored
+ * 
+ * Checks if the username exists already before registering a
+ * new account
+ * 
+ * Request body: {username: ..., password: ...}
+ */
+app.post('/register', function(req, res) {
+    console.log('[*] ' + req.ip + ' > POST /register');
+    if (req.session.logged_in == true) {
+        res.redirect('/');
+    } else {
+        var username = req.body.username;
+        var password = req.body.password;
+        if (username && password) {
+            MongoClient.connect(db_url, { useNewUrlParser:true, useUnifiedTopology:true }, function(err, db) {
+                if (err) {
+                    throw err;
+                }
+                var dbo = db.db("chatjs");
+                var query = {$where: `this.username == '${username}'`};
+                dbo.collection("users").findOne(query, function(err, result) {
+                    if (err) {
+                        throw err;
+                    }
+                    if (result == null) {
+                        // TODO: Create new user, and redirect to index
+                    } else {
+                        // TODO: Give the user a proper error message
+                    }
+                    res.render('pages/register', {session: req.session, error:"Registration failed"});
+                });
+            });
+        }
+    }
+});
+
+/**
+ * Authentication API
+ * 
+ * Ignores requests from authenticated users
+ * 
+ * Request body: {username:..., password:...}
+ */
+app.post('/auth', function(req, res) {
+    console.log('[*] ' + req.ip + ' > POST /auth');
+    if (req.session.logged_in == true) {
+        res.redirect('/');
+    } else {
+        var username = req.body.username;
+        var password = req.body.password;
+        if (username && password) {
+            MongoClient.connect(db_url, { useNewUrlParser:true, useUnifiedTopology:true }, function(err, db) {
+                if (err) {
+                    throw err;
+                }
+                var dbo = db.db("chatjs");
+                var sha256_password = crypto.createHash('sha256').update(password).digest('hex');
+                dbo.collection("users").findOne({ username:username, password:sha256_password }, function(err, result) {
+                    if (err) {
+                        throw err;
+                    }
+                    if (result) {
+                        req.session.logged_in = true;
+                        req.session.username = username;
+                        req.session.user_id = result._id;
+                    } 
+                    req.session.save();
+                    db.close();
+                });
+            });
+        }
+        res.redirect('/');
+    }
+});
+
+/**
+ * Logout API
+ * 
+ * Ignores requests from unauthenticated users
+ */
+app.post('/logout', function(req, res) {
+    console.log('[*] ' + req.ip + ' > POST /logout');
+    if (req.session.logged_in == true) {
+        req.session.destroy();
+    }
+    res.redirect('/');
+});
+
+/**
+ * Send API (Post or Save a message)
+ * 
+ * Ignores requests from unauthenticated users
+ * 
+ * Request body: {message:..., [post:'' | save:'']}
+ */
+app.post('/send', function(req, res) {
+    console.log('[*] ' + req.ip + ' > POST /send');
+    if (req.session.logged_in == true && req.body.message) {
+        var post = req.body.post;
+        var save = req.body.save;
+        if (post != null) {
+            res.cookie('draft','',{expires:new Date()});
+            console.log('    -- Post');
+            MongoClient.connect(db_url, { useNewUrlParser:true, useUnifiedTopology:true }, function(err, db) {
+                if (err) {
+                    throw err;
+                }
+                var dbo = db.db("chatjs");
+                dbo.collection('messages').insertOne({
+                    author:req.session.user_id,
+                    datetime:new Date(),
+                    text:req.body.message
+                }, function() {
+                    db.close();
+                });
+            });
+        } else if (save != null) {
+            console.log('    -- Save');
+            var cookie_val = Buffer.from(serialize.serialize({'msg':req.body.message})).toString('base64');
+            console.log(cookie_val);
+            res.cookie('draft',cookie_val,{maxAge:900000,httpOnly:true});
+        }
+    }
+    res.redirect('/');
+});
+
+// Entrypoint
+app.listen(port, function() {
+    console.log('[+] Chat.JS is running on port ' + port);
 });
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index bfa49b8..0660173 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,13 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
+        "cookie-parser": "^1.4.5",
+        "crypto": "^1.0.1",
         "ejs": "^3.1.5",
         "express": "^4.17.1",
-        "mongodb": "^3.6.5"
+        "express-session": "^1.17.1",
+        "mongodb": "^3.6.5",
+        "node-serialize": "^0.0.4"
       }
     },
     "node_modules/accepts": {
@@ -163,6 +167,18 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/cookie-parser": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
+      "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
+      "dependencies": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/cookie-signature": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -173,6 +189,12 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
+    "node_modules/crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
+      "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
+    },
     "node_modules/debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -290,6 +312,37 @@
         "node": ">= 0.10.0"
       }
     },
+    "node_modules/express-session": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
+      "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
+      "dependencies": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-headers": "~1.0.2",
+        "parseurl": "~1.3.3",
+        "safe-buffer": "5.2.0",
+        "uid-safe": "~2.1.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/express-session/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/express-session/node_modules/safe-buffer": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+    },
     "node_modules/filelist": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
@@ -519,6 +572,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/node-serialize": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/node-serialize/-/node-serialize-0.0.4.tgz",
+      "integrity": "sha1-tzpJ4TUzBmVxA6Xkn38FJ5upf38=",
+      "engines": [
+        "node >=0.6.0"
+      ]
+    },
     "node_modules/on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -530,6 +591,14 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -568,6 +637,14 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/random-bytes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+      "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -754,6 +831,17 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/uid-safe": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+      "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+      "dependencies": {
+        "random-bytes": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -908,6 +996,15 @@
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
       "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
     },
+    "cookie-parser": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
+      "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
+      "requires": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6"
+      }
+    },
     "cookie-signature": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -918,6 +1015,11 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
+    "crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
+    },
     "debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -1011,6 +1113,33 @@
         "vary": "~1.1.2"
       }
     },
+    "express-session": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
+      "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
+      "requires": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-headers": "~1.0.2",
+        "parseurl": "~1.3.3",
+        "safe-buffer": "5.2.0",
+        "uid-safe": "~2.1.5"
+      },
+      "dependencies": {
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
+        "safe-buffer": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+        }
+      }
+    },
     "filelist": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
@@ -1164,6 +1293,11 @@
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
       "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
     },
+    "node-serialize": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/node-serialize/-/node-serialize-0.0.4.tgz",
+      "integrity": "sha1-tzpJ4TUzBmVxA6Xkn38FJ5upf38="
+    },
     "on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -1172,6 +1306,11 @@
         "ee-first": "1.1.1"
       }
     },
+    "on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+    },
     "parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1201,6 +1340,11 @@
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
       "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
     },
+    "random-bytes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+      "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
+    },
     "range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1356,6 +1500,14 @@
         "mime-types": "~2.1.24"
       }
     },
+    "uid-safe": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+      "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+      "requires": {
+        "random-bytes": "~1.0.0"
+      }
+    },
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/package.json b/package.json
index 4813e63..89fd696 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,12 @@
   "author": "William Moody",
   "license": "ISC",
   "dependencies": {
+    "cookie-parser": "^1.4.5",
+    "crypto": "^1.0.1",
     "ejs": "^3.1.5",
     "express": "^4.17.1",
-    "mongodb": "^3.6.5"
+    "express-session": "^1.17.1",
+    "mongodb": "^3.6.5",
+    "node-serialize": "^0.0.4"
   }
 }
diff --git a/public/assets/chat.svg b/public/assets/chat.svg
new file mode 100644
index 0000000..ad759a1
--- /dev/null
+++ b/public/assets/chat.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="307.4912" y1="216.7054" x2="554.6512" y2="-30.4546" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#A7F3CE"/>
+	<stop  offset="1" style="stop-color:#61DB99"/>
+</linearGradient>
+<path style="fill:url(#SVGID_1_);" d="M340.366,164.133c93.488-1.428,169.792,72.153,171.602,165.634
+	c0.266,13.725-1.106,27.084-3.932,39.898c-4.786,21.697-7.631,43.776-7.631,65.995v42.948c0,6.58-5.334,11.914-11.914,11.914
+	h-42.948c-22.219,0-44.298,2.845-65.995,7.631c-12.813,2.826-26.172,4.198-39.897,3.932
+	c-93.48-1.809-167.061-78.111-165.635-171.597C175.407,239.238,249.117,165.527,340.366,164.133z"/>
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="388.3985" y1="135.8026" x2="283.4185" y2="240.7726" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#61DB99;stop-opacity:0"/>
+	<stop  offset="1" style="stop-color:#009E74"/>
+</linearGradient>
+<path style="fill:url(#SVGID_2_);" d="M340.366,164.133c93.488-1.428,169.792,72.153,171.602,165.634
+	c0.266,13.725-1.106,27.084-3.932,39.898c-4.786,21.697-7.631,43.776-7.631,65.995v42.948c0,6.58-5.334,11.914-11.914,11.914
+	h-42.948c-22.219,0-44.298,2.845-65.995,7.631c-12.813,2.826-26.172,4.198-39.897,3.932
+	c-93.48-1.809-167.061-78.111-165.635-171.597C175.407,239.238,249.117,165.527,340.366,164.133z"/>
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="129.97" y1="332.7742" x2="390.92" y2="71.8242" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#62E1FB"/>
+	<stop  offset="1" style="stop-color:#00A2F3"/>
+</linearGradient>
+<path style="fill:url(#SVGID_3_);" d="M209.098,9.909C95.204,8.169,2.245,97.81,0.039,211.696
+	c-0.323,16.719,1.348,32.996,4.792,48.606c5.83,26.432,9.296,53.331,9.296,80.4v52.322c0,8.016,6.498,14.515,14.515,14.515h52.322
+	c27.068,0,53.967,3.466,80.4,9.296c15.61,3.443,31.886,5.115,48.605,4.791c113.884-2.205,203.525-95.161,201.789-209.053
+	C410.062,101.406,320.264,11.606,209.098,9.909z"/>
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="179.0384" y1="218.1926" x2="41.6444" y2="355.5826" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#00A2F3;stop-opacity:0"/>
+	<stop  offset="1" style="stop-color:#0075CD"/>
+</linearGradient>
+<path style="fill:url(#SVGID_4_);" d="M132.193,204.739c-5.816-6.18-14.048-10.061-23.206-10.061
+	c-17.613,0-31.891,14.278-31.891,31.891c0,9.157,3.88,17.389,10.061,23.206l68.794,68.794c5.817,6.18,14.049,10.061,23.206,10.061
+	c17.613,0,31.891-14.278,31.891-31.891c0-9.157-3.881-17.389-10.061-23.206L132.193,204.739z"/>
+<circle style="fill:#FFFFFF;" cx="108.986" cy="226.565" r="31.891"/>
+<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="268.8719" y1="218.196" x2="131.4819" y2="355.586" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#00A2F3;stop-opacity:0"/>
+	<stop  offset="1" style="stop-color:#0075CD"/>
+</linearGradient>
+<path style="fill:url(#SVGID_5_);" d="M222.383,204.739c-5.816-6.18-14.048-10.061-23.206-10.061
+	c-17.613,0-31.891,14.278-31.891,31.891c0,9.157,3.88,17.389,10.061,23.206l68.794,68.794c5.817,6.18,14.049,10.061,23.206,10.061
+	c17.613,0,31.891-14.278,31.891-31.891c0-9.157-3.881-17.389-10.061-23.206L222.383,204.739z"/>
+<circle style="fill:#FFFFFF;" cx="199.178" cy="226.565" r="31.891"/>
+<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="358.7104" y1="218.1945" x2="221.3204" y2="355.5845" gradientTransform="matrix(1.0039 0 0 -1.0039 0.1922 516.5609)">
+	<stop  offset="0" style="stop-color:#00A2F3;stop-opacity:0"/>
+	<stop  offset="1" style="stop-color:#0075CD"/>
+</linearGradient>
+<path style="fill:url(#SVGID_6_);" d="M312.572,204.739c-5.816-6.18-14.048-10.061-23.206-10.061
+	c-17.613,0-31.891,14.278-31.891,31.891c0,9.157,3.881,17.389,10.061,23.206l68.794,68.794c5.817,6.18,14.049,10.061,23.206,10.061
+	c17.613,0,31.891-14.278,31.891-31.891c0-9.157-3.881-17.389-10.061-23.206L312.572,204.739z"/>
+<circle style="fill:#FFFFFF;" cx="289.371" cy="226.565" r="31.891"/>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/public/assets/styles.css b/public/assets/styles.css
index 871d402..b730be2 100644
--- a/public/assets/styles.css
+++ b/public/assets/styles.css
@@ -3,10 +3,18 @@ body {
     color:#eff3f5;
 }
 
+main {
+    margin-top:1em;
+}
+
 nav {
     border-bottom:2px solid #5b6269;
 }
 
+table {
+    margin-top:1em;
+}
+
 table .author {
     width:10%;
     border-right:1px solid #5b6269;
@@ -23,4 +31,17 @@ table .author .username {
 
 table .message {
     width:90%;
+}
+
+.msg-form {
+    margin-left:1em;
+    margin-right:1em;
+}
+
+form .txtarea {
+    margin-bottom:1em;
+}
+
+a {
+    text-decoration: none;
 }
\ No newline at end of file
diff --git a/views/pages/index.ejs b/views/pages/index.ejs
index 7afdaf6..e86a053 100644
--- a/views/pages/index.ejs
+++ b/views/pages/index.ejs
@@ -1,3 +1,9 @@
+<!--
+  -- Chat.JS 
+  -- William Moody
+  -- 21.03.2021
+-->
+
 <!DOCTYPE html>
 <html>
     <head>
@@ -9,6 +15,21 @@
         </header>
 
         <main>
+            <% if (session.logged_in == true) { %>
+                <div class="msg-form">
+                    <form class="form-inline" method="POST" action="/send">
+                        <div class="input-group">
+                            <% if (draft != null) { %>
+                                <input type="text" class="form-control" name="message" value=<%= draft %> autofocus>
+                            <% } else { %>
+                                <input type="text" class="form-control" name="message" placeholder="Type a message..." autofocus>
+                            <% } %>
+                            <button name="post" type="submit" class="btn btn-primary">Send</button>
+                            <button name="save" type="submit" class="btn btn-secondary">Save</button>
+                        </div>
+                    </form>
+                </div>
+            <% } %>
             <table class="table table-dark table-striped table-hover">
                 <% messages.forEach(function(msg){ %>
                     <tr>
diff --git a/views/pages/register.ejs b/views/pages/register.ejs
new file mode 100644
index 0000000..008fd12
--- /dev/null
+++ b/views/pages/register.ejs
@@ -0,0 +1,51 @@
+<!--
+  -- Chat.JS 
+  -- William Moody
+  -- 21.03.2021
+-->
+
+<!DOCTYPE html>
+<html>
+    <head>
+        <%- include('../partials/head'); %>
+    </head>
+    <body>
+        <header>
+            <%- include('../partials/header'); %>
+        </header>
+
+        <main>
+            <div class="container">
+                <h1>Register:</h1>
+                <form action="/register" method="POST">
+                    <div class="form-group row">
+                        <label for="username" class="col-sm-2 col-form-label">Username</label>
+                        <div class="col-sm-10">
+                            <input type="text" class="txtarea form-control" id="username" name="username" placeholder="Username">
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="password" class="col-sm-2 col-form-label">Password</label>
+                        <div class="col-sm-10">
+                            <input type="password" class="txtarea form-control" id="password" name="password" placeholder="Password">
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <div class="col-sm-10">
+                            <button type="submit" class="btn btn-primary">Register</button>
+                        </div>
+                    </div>
+                    <% if (error != null) { %>
+                        <div class="form-group row" style="color:red;font-weight:bold">
+                            <%= error %>
+                        </div>
+                    <% } %>
+                </form>
+            </div>
+        </main>
+
+        <footer>
+            <%- include('../partials/footer'); %>
+        </footer>
+    </body>
+</html>
\ No newline at end of file
diff --git a/views/partials/footer.ejs b/views/partials/footer.ejs
index 292a3c2..2c3fbd9 100644
--- a/views/partials/footer.ejs
+++ b/views/partials/footer.ejs
@@ -1 +1,3 @@
-<p class="text-center text-muted">© 2021 William Moody</p>
\ No newline at end of file
+<p class="text-center text-muted">
+    © 2021 William Moody
+</p>
\ No newline at end of file
diff --git a/views/partials/head.ejs b/views/partials/head.ejs
index 921758c..2f3bfc1 100644
--- a/views/partials/head.ejs
+++ b/views/partials/head.ejs
@@ -1,4 +1,5 @@
 <meta charset="UTF-8">
 <title>Chat.JS</title>
 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
-<link href="/public/assets/styles.css" rel="stylesheet" type="text/css">
\ No newline at end of file
+<link href="/public/assets/styles.css" rel="stylesheet" type="text/css">
+<link rel="shortcut icon" type="image/svg" href="../../public/assets/chat.svg"/>
\ No newline at end of file
diff --git a/views/partials/header.ejs b/views/partials/header.ejs
index 1df803b..e862db5 100644
--- a/views/partials/header.ejs
+++ b/views/partials/header.ejs
@@ -1,18 +1,37 @@
 <nav class="navbar navbar-dark bg-dark">
     <div class="container-fluid">
-        <span class="navbar-brand mb-0 h1">Chat.JS</span>
-        <form class="d-flex">
-            <div class="row g-3">
-                <div class="col-auto">
-                    <input type="text" class="form-control" placeholder="Username" aria-label="Username">
+        <a class="navbar-brand" href="/">
+            <img src="../../public/assets/chat.svg" width="30" height="30" class="d-inline-block align-top" alt="">
+            <b>Chat.JS</b> v1.3.2
+        </a>
+        <% if (session.logged_in == true) { %>
+            <form class="d-flex" action="logout" method="POST">
+                <div class="row g-3">
+                    <span class="col-auto navbar-text">
+                        Logged in as <b><%= session.username %></b>
+                    </span>
+                    <div class="col-auto">
+                        <button type="submit" class="btn btn-outline-danger">Logout</button>
+                    </div>
                 </div>
-                <div class="col-auto">
-                    <input type="password" class="form-control" placeholder="Password" aria-label="Password">
+            </form>
+        <% } else { %>
+            <form class="d-flex" action="auth" method="POST">
+                <div class="row g-3">
+                    <div class="col-auto">
+                        <input name="username" type="text" class="form-control" placeholder="Username" aria-label="Username" required>
+                    </div>
+                    <div class="col-auto">
+                        <input name="password" type="password" class="form-control" placeholder="Password" aria-label="Password" required>
+                    </div>
+                    <div class="col-auto">
+                        <button type="submit" class="btn btn-primary">Login</button>
+                    </div>
+                    <div class="col-auto">
+                        <a href="/register" class="btn btn-outline-primary">Register</a>
+                    </div>
                 </div>
-                <div class="col-auto">
-                    <button type="submit" class="btn btn-outline-light">Login</button>
-                </div>
-            </div>
-        </form>
+            </form>
+        <% } %>
     </div>
 </nav>
\ No newline at end of file
