aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig9
-rw-r--r--src/db/DBController.ts10
-rw-r--r--src/db/tables.sql30
-rw-r--r--src/db/user.ts76
-rw-r--r--src/groups/user.ts7
-rw-r--r--src/handler/admin.ts12
-rw-r--r--src/handler/fileView.ts6
-rw-r--r--src/handler/user.ts96
-rw-r--r--src/main.ts21
-rw-r--r--src/public/css/style.css8
-rw-r--r--src/public/fonts/ShareTechMono.ttfbin0 -> 42756 bytes
-rw-r--r--src/public/index.html11
-rw-r--r--src/public/js/login.js18
-rw-r--r--src/public/js/setup.js21
-rw-r--r--src/public/script.js0
-rw-r--r--src/public/style.css0
-rw-r--r--src/public/test.html13
-rw-r--r--src/util/files.ts28
-rw-r--r--src/util/server.ts14
-rw-r--r--src/util/user.ts30
-rw-r--r--src/views/admin.ejs32
-rw-r--r--src/views/index.ejs13
-rw-r--r--src/views/index.html11
-rw-r--r--src/views/login.ejs24
-rw-r--r--src/views/test.html13
25 files changed, 373 insertions, 130 deletions
diff --git a/.editorconfig b/.editorconfig
index d241554..97d37ab 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,12 +1,9 @@
root = true
-[*.ts]
-end_of_line = lf
-insert_final_newline = true
-indent_style = space
-indent_size = 4
+[*]
+charset = utf-8
-[*.html]
+[*.{ts, html, ejs}]
end_of_line = lf
insert_final_newline = true
indent_style = space
diff --git a/src/db/DBController.ts b/src/db/DBController.ts
index cb714b3..0b212e7 100644
--- a/src/db/DBController.ts
+++ b/src/db/DBController.ts
@@ -1,5 +1,5 @@
import { Client } from "https://deno.land/x/mysql/mod.ts";
-import { readFileStr } from "https://deno.land/std/fs/mod.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
export default class DBController {
private client?: Client;
@@ -7,13 +7,13 @@ export default class DBController {
async init() {
await this.connect();
try {
- const sql = await readFileStr("./src/db/tables.sql");
+ const sql = await Deno.readTextFile("./src/db/tables.sql");
const queries = sql.split(";");
queries.pop();
for (const query of queries) await this.execute(query);
- console.log("Tables created");
+ log.info("Tables created");
} catch (e) {
- console.error("Could not create tables");
+ log.error("Could not create tables");
throw e;
}
}
@@ -28,7 +28,7 @@ export default class DBController {
});
return this.client;
} catch (e) {
- console.error("Could not connect to database");
+ log.error("Could not connect to database");
throw e;
}
}
diff --git a/src/db/tables.sql b/src/db/tables.sql
index a0c9eef..a7c3838 100644
--- a/src/db/tables.sql
+++ b/src/db/tables.sql
@@ -1,19 +1,23 @@
# DROP TABLE IF EXISTS access;
# DROP TABLE IF EXISTS users;
-CREATE TABLE IF NOT EXISTS users (
- id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- email VARCHAR(48) NOT NULL UNIQUE,
- username VARCHAR(24) NOT NULL UNIQUE,
- password VARCHAR(64) NOT NULL,
+CREATE TABLE IF NOT EXISTS users
+(
+ id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ email VARCHAR(48) NOT NULL UNIQUE,
+ username VARCHAR(24) NOT NULL UNIQUE,
+ password VARCHAR(64) NOT NULL,
verification VARCHAR(64) NOT NULL UNIQUE,
- dark_theme BOOLEAN NOT NULL DEFAULT true,
- is_admin BOOLEAN NOT NULL DEFAULT false
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ dark_theme BOOLEAN NOT NULL DEFAULT true,
+ is_admin BOOLEAN NOT NULL DEFAULT false
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
-CREATE TABLE IF NOT EXISTS access (
- id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- uid INT(6) UNSIGNED,
+CREATE TABLE IF NOT EXISTS access
+(
+ id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ uid INT(6) UNSIGNED,
path VARCHAR(64) NOT NULL,
- FOREIGN KEY (uid) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ FOREIGN KEY (uid) REFERENCES users (id) ON DELETE CASCADE
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
diff --git a/src/db/user.ts b/src/db/user.ts
index 9152383..4e5a76c 100644
--- a/src/db/user.ts
+++ b/src/db/user.ts
@@ -3,6 +3,7 @@ import { hash, compare, genSalt } from "https://deno.land/x/bcrypt/mod.ts";
class User {
private controller: DBController;
+
constructor() {
this.controller = new DBController();
}
@@ -17,7 +18,7 @@ class User {
async createUser(email: string, username: string, password: string, isAdmin = false): Promise<boolean> {
const salt = await genSalt(12);
const passwordHash = await hash(password, salt);
- const verification = this.generateId();
+ const verification = User.generateId();
try {
await this.controller.execute(
"INSERT INTO users (email, username, password, verification, is_admin) VALUE (?, ?, ?, ?, ?)",
@@ -35,21 +36,22 @@ class User {
* @param plainTextPassword
*/
async login(username: string, plainTextPassword: string): Promise<loginData> {
- const { uid, password, verification, darkTheme } = (
- await this.controller.query(
- "SELECT id as uid, password, verification, dark_theme as darkTheme FROM users WHERE username = ?",
- [username]
- )
- )[0];
- if (compare(plainTextPassword, password)) {
+ try {
+ const { uid, password, verification, darkTheme } = (
+ await this.controller.query(
+ "SELECT id uid, password, verification, dark_theme darkTheme FROM users WHERE username = ?",
+ [username]
+ )
+ )[0]; // Will throw an error if user does not exist => good to go?
+ if (!compare(plainTextPassword, password)) return { success: false };
return {
success: true,
uid,
darkTheme,
verification,
};
- } else {
- return { success: false };
+ } catch (e) {
+ throw e;
}
}
@@ -59,15 +61,16 @@ class User {
* @param uid
* @param userVerification
*/
- async getUserByVerificationId(uid: number, userVerification: string): Promise<userData | undefined> {
+ async getUserByVerificationId(uid?: number, userVerification?: string): Promise<userData | undefined> {
try {
+ if (!uid || !userVerification || uid < 1 || userVerification.length !== 64) throw new TypeError("Wrong parameters");
const user = (
await this.controller.query(
"SELECT id, email, username, verification, dark_theme darkTheme, is_admin isAdmin FROM users WHERE id = ? AND verification = ?",
[uid, userVerification]
)
)[0];
- if (user) return user as userData;
+ return user as userData;
} catch (e) {
throw e;
}
@@ -86,6 +89,20 @@ class User {
}
/**
+ * Gets user theme
+ * @param uid
+ */
+ async getUserTheme(uid: number): Promise<boolean> {
+ try {
+ const users = await this.controller.query("SELECT dark_theme FROM users WHERE id = ?", [uid]);
+ if (users.length > 0) return users[0].dark_theme;
+ return true;
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ /**
* Sets admin status of a user
* @param uid
* @param isAdmin
@@ -99,12 +116,45 @@ class User {
}
/**
+ *
+ * @param {number} uid
+ * @returns {Promise<boolean>}
+ */
+ async isAdmin(uid: number): Promise<boolean> {
+ try {
+ const user = (await this.controller.query("SELECT is_admin FROM users WHERE id = ?", [uid]))[0];
+ return user.is_admin;
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ /**
+ *
+ * @param {number} uid
+ * @param {string} currentPassword
+ * @param {string} newPassword
+ * @returns {Promise<void>}
+ */
+ async changePassword(uid: number, currentPassword: string, newPassword: string) {
+ try {
+ const userPassword = (await this.controller.query("SELECT password FROM users WHERE id = ?", [uid]))[0];
+ if (!compare(currentPassword, userPassword)) throw new Error("Passwords do not match");
+ const salt = await genSalt(12);
+ const passwordHash = await hash(newPassword, salt);
+ await this.controller.execute("UPDATE users SET password = ? WHERE id = ?", [passwordHash, uid]);
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ /**
* Generate random id
* @param len
* @private
*/
// TODO: Improve
- private generateId(len = 64): string {
+ static generateId(len = 64): string {
const values = new Uint8Array(len / 2);
crypto.getRandomValues(values);
return Array.from(values, (dec) => ("0" + dec.toString(36)).substr(-2)).join("");
diff --git a/src/groups/user.ts b/src/groups/user.ts
index b8518dc..e0c7359 100644
--- a/src/groups/user.ts
+++ b/src/groups/user.ts
@@ -1,8 +1,11 @@
-import type { Group, Context } from "https://deno.land/x/abc@master/mod.ts";
+import type { Group } from "https://deno.land/x/abc@master/mod.ts";
import * as handlers from "../handler/user.ts";
export default function (g: Group) {
- g.get("/:name", handlers.index);
+ g.get("/login", handlers.renderLogin);
g.post("/register", handlers.register);
g.post("/login", handlers.login);
+ g.any("/logout", handlers.logout);
+ g.put("/theme", handlers.changeTheme);
+ g.put("/password", handlers.updatePassword);
}
diff --git a/src/handler/admin.ts b/src/handler/admin.ts
new file mode 100644
index 0000000..a626e50
--- /dev/null
+++ b/src/handler/admin.ts
@@ -0,0 +1,12 @@
+import type { HandlerFunc, Context } from "https://deno.land/x/abc@master/mod.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
+import { isAdmin } from "../util/user.ts";
+import { isSetup } from "../util/server.ts";
+
+export const render: HandlerFunc = async (c: Context) => {
+ if (await isAdmin(c))
+ return await c.render("./src/views/admin.ejs", { initial: false });
+ else if (!(await isSetup()))
+ return await c.render("./src/views/admin.ejs", { initial: true });
+ return c.redirect("/");
+}
diff --git a/src/handler/fileView.ts b/src/handler/fileView.ts
index eb9c3a1..29bc7a9 100644
--- a/src/handler/fileView.ts
+++ b/src/handler/fileView.ts
@@ -1,6 +1,8 @@
import type { HandlerFunc, Context } from "https://deno.land/x/abc@master/mod.ts";
import { getFiles } from "../util/files.ts";
+import { getCurrentUser } from "../util/user.ts";
export const handlePath: HandlerFunc = async (c: Context) => {
- return await c.render("./src/views/index.html", { files: await getFiles(c.path) });
-};
+ if (!(await getCurrentUser(c))) return c.redirect("/user/login"); // TODO: Handle shared files
+ return await c.render("./src/views/index.ejs", { files: await getFiles(c) });
+}
diff --git a/src/handler/user.ts b/src/handler/user.ts
index e194008..2c3ac8d 100644
--- a/src/handler/user.ts
+++ b/src/handler/user.ts
@@ -1,25 +1,85 @@
import type { HandlerFunc, Context } from "https://deno.land/x/abc@master/mod.ts";
-import db, {loginData} from "../db/user.ts";
+import db, { loginData } from "../db/user.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
+import { getCurrentUser, isAdmin } from "../util/user.ts";
+import { isSetup } from "../util/server.ts";
+import { deleteCookie } from "https://deno.land/std/http/cookie.ts";
+
-export const index: HandlerFunc = async (c: Context) => c.params.name;
export const register: HandlerFunc = async (c: Context) => {
- const { username, email, password } = await c.body();
- const success = await db.createUser(email, username, password);
- // TODO: Send email
- return {success};
-};
+ if (!(await isAdmin(c)) && await isSetup()) return { success: false }; // I'm tired: not sure if this works
+ // TODO: How to handle register
+ const { username, email, password, admin } = await c.body();
+ try {
+ const success = await db.createUser(email, username, password, admin !== undefined ? admin : false);
+ return { success };
+ } catch (e) {
+ return { success: false };
+ }
+}
+export const renderLogin: HandlerFunc = async (c: Context) => {
+ if (await getCurrentUser(c)) return c.redirect("/");
+ return await c.render("./src/views/login.ejs");
+}
export const login: HandlerFunc = async (c: Context) => {
const { username, password } = await c.body();
- const data: loginData = await db.login(username, password);
- if (data.success) {
- c.setCookie({
- name: "uid",
- value: data.uid!.toString(),
- });
- c.setCookie({
- name: "verification",
- value: data.verification!,
- })
+ try {
+ const data: loginData = await db.login(username, password);
+ if (data.success) {
+ c.setCookie({
+ name: "uid",
+ value: data.uid!.toString(),
+ path: "/",
+ });
+ c.setCookie({
+ name: "verification",
+ value: data.verification!,
+ path: "/",
+ });
+ }
+ return { success: data.success };
+ } catch (e) {
+ log.error(e);
+ return { success: false };
+ }
+}
+export const logout: HandlerFunc = async (c: Context) => {
+ deleteCookie(c.response, "uid");
+ deleteCookie(c.response, "verification");
+ c.redirect("/");
+}
+export const changeTheme: HandlerFunc = async (c: Context) => {
+ try {
+ const currentUser = await getCurrentUser(c);
+ if (!currentUser) return { success: false };
+ await db.changeTheme(currentUser.id);
+ return { success: true };
+ } catch (e) {
+ log.error(e);
+ return { success: false };
+ }
+}
+export const setAdmin: HandlerFunc = async (c: Context) => {
+ const { uid, state } = await c.body();
+ try {
+ const currentUser = await getCurrentUser(c);
+ if (!(currentUser && currentUser.isAdmin)) return { success: false };
+ await db.setAdminState(uid, state);
+ return { success: true };
+ } catch (e) {
+ log.error(e);
+ return { success: false };
+ }
+}
+export const updatePassword: HandlerFunc = async (c: Context) => {
+ const currentUser = await getCurrentUser(c);
+ if (!currentUser) return { success: false };
+ const { currentPassword, newPassword } = await c.body();
+ try {
+ await db.changePassword(currentUser.id, currentPassword, newPassword);
+ return { success: true };
+ } catch (e) {
+ log.error(e);
+ return { success: false };
}
- return {"success": data.success};
}
diff --git a/src/main.ts b/src/main.ts
index 99f605c..bf5309c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,11 +3,13 @@ import { Application } from "https://deno.land/x/abc@master/mod.ts";
import type { Context } from "https://deno.land/x/abc@master/mod.ts";
import { renderFile } from "https://deno.land/x/dejs/mod.ts";
import * as groups from "./groups/index.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
import { handlePath } from "./handler/fileView.ts";
+import { render as renderAdmin } from "./handler/admin.ts";
import DBController from "./db/DBController.ts";
+import { getCurrentUser } from "./util/user.ts";
-// Ugly solution
-(async () => await new DBController().init())();
+new DBController().init().then();
const port = parseInt(Deno.env.get("PORT") || "8080");
const app = new Application();
@@ -18,16 +20,19 @@ app.renderer = {
},
};
-app.static("/public/", "./src/public/"); // Manage static files
-app.get("/", async (c: Context) => await c.render("./src/views/index.html")); // Render index on /
+app.static("/", "./src/public/");
+app.get("/", async (c: Context) => await c.render("./src/views/index.ejs", {
+ files: undefined,
+ user: getCurrentUser(c)
+}));
app.get("/files/*", handlePath);
app.get("/files/", handlePath);
+app.get("/admin/", renderAdmin);
+
// Load groups dynamically
-// deno-lint-ignore ban-ts-comment
-// @ts-ignore
-for (let groupName in groups) groups[groupName](app.group(groupName));
+for (const groupName in groups) (groups as { [key: string]: Function })[groupName](app.group(groupName));
app.start({ port });
-console.log(`Server listening on http://localhost:${port}`);
+log.info(`Server listening on http://localhost:${port}`);
diff --git a/src/public/css/style.css b/src/public/css/style.css
index e69de29..d72bbc5 100644
--- a/src/public/css/style.css
+++ b/src/public/css/style.css
@@ -0,0 +1,8 @@
+@font-face {
+ font-family: "Share Tech Mono";
+ src: url("/fonts/ShareTechMono.ttf") format("TrueType");
+}
+
+body {
+ font-family: "Share Tech Mono", monospace;
+} \ No newline at end of file
diff --git a/src/public/fonts/ShareTechMono.ttf b/src/public/fonts/ShareTechMono.ttf
new file mode 100644
index 0000000..c8e530f
--- /dev/null
+++ b/src/public/fonts/ShareTechMono.ttf
Binary files differ
diff --git a/src/public/index.html b/src/public/index.html
deleted file mode 100644
index 99a87cd..0000000
--- a/src/public/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width" />
- <title>Index</title>
- </head>
- <body>
- body
- </body>
-</html>
diff --git a/src/public/js/login.js b/src/public/js/login.js
new file mode 100644
index 0000000..681b02f
--- /dev/null
+++ b/src/public/js/login.js
@@ -0,0 +1,18 @@
+const form = document.getElementById("login-form");
+
+form.addEventListener('submit', async e => {
+ e.preventDefault();
+ const username = document.getElementById("username").value;
+ const password = document.getElementById("password").value;
+
+ const body = JSON.stringify({username, password});
+
+ const resp = await fetch("/user/login", {
+ method: "POST",
+ headers: {'Content-Type': 'application/json'},
+ body,
+ });
+ const res = await resp.json();
+ if (res.success) location.replace("/");
+ else alert("ASJHDOAISJDLKAJSD");
+}) \ No newline at end of file
diff --git a/src/public/js/setup.js b/src/public/js/setup.js
new file mode 100644
index 0000000..ef31e18
--- /dev/null
+++ b/src/public/js/setup.js
@@ -0,0 +1,21 @@
+const form = document.getElementById("setup-form");
+
+form.addEventListener('submit', async e => {
+ e.preventDefault();
+ const username = document.getElementById('username').value;
+ const email = document.getElementById('email').value;
+ const password = document.getElementById('password').value;
+ const admin = true;
+
+ const body = JSON.stringify({username, email, password, admin});
+
+ const resp = await fetch("/user/register", {
+ method: "POST",
+ headers: {'Content-Type': 'application/json'},
+ body,
+ })
+ const res = await resp.json();
+ if (res.success) location.replace("/user/login");
+ else alert("ASJHDOAISJDLKAJSD");
+
+}) \ No newline at end of file
diff --git a/src/public/script.js b/src/public/script.js
deleted file mode 100644
index e69de29..0000000
--- a/src/public/script.js
+++ /dev/null
diff --git a/src/public/style.css b/src/public/style.css
deleted file mode 100644
index e69de29..0000000
--- a/src/public/style.css
+++ /dev/null
diff --git a/src/public/test.html b/src/public/test.html
deleted file mode 100644
index b188db3..0000000
--- a/src/public/test.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <title>Title</title>
- </head>
- <body>
- <h1>Home :)</h1>
- <% if (name) { %>
- <h1>hello, <%= name %>!</h1>
- <% } %>
- </body>
-</html>
diff --git a/src/util/files.ts b/src/util/files.ts
index 49c5ebb..a1ce5d9 100644
--- a/src/util/files.ts
+++ b/src/util/files.ts
@@ -1,27 +1,25 @@
import { walk, ensureDirSync, existsSync } from "https://deno.land/std/fs/mod.ts";
+import type { Context } from "https://deno.land/x/abc@master/mod.ts";
+import { getUserCookies } from "./user.ts";
-const TEMP_USER_ID = 42; // TODO: FIX
-
-export const cleanPath = (path: string): string => {
- createUserDirectory(TEMP_USER_ID);
-
+export const cleanPath = (path: string, uid: number): string => {
return (
"data/" +
- TEMP_USER_ID +
+ uid +
"/" +
path
- .replace("/files/", "")
+ .replace(/\/files\/?/, "")
.replace("../", "") // TODO: Fix relative ../
.replace("./", "")
.replace(/([^:]\/)\/+/g, "$1")
);
-};
-
-export const getFiles = async (path: string) => {
- const newPath = path ? path : "";
+}
- createUserDirectory(TEMP_USER_ID);
- const dataPath: string = cleanPath(newPath);
+export const getFiles = async (c: Context) => {
+ const path = c.path ? c.path : "";
+ const uid = getUserCookies(c).uid;
+ createUserDirectory(uid); // TODO: Consider doing this in db/user/createUser => performance?
+ const dataPath: string = cleanPath(path, uid);
if (!existsSync(dataPath)) return [];
@@ -30,9 +28,9 @@ export const getFiles = async (path: string) => {
files.push(entry.path);
}
return files;
-};
+}
export const createUserDirectory = (uid: number) => {
ensureDirSync("data/" + uid);
// TODO: Give user access to dir
-};
+}
diff --git a/src/util/server.ts b/src/util/server.ts
new file mode 100644
index 0000000..665c14f
--- /dev/null
+++ b/src/util/server.ts
@@ -0,0 +1,14 @@
+import DBController from "../db/DBController.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
+
+const controller = new DBController();
+
+export const isSetup = async (): Promise<boolean> => {
+ try {
+ const users = await controller.query("SELECT id FROM users");
+ return users.length > 0
+ } catch (e) {
+ log.error(e);
+ return false;
+ }
+}
diff --git a/src/util/user.ts b/src/util/user.ts
new file mode 100644
index 0000000..f632a11
--- /dev/null
+++ b/src/util/user.ts
@@ -0,0 +1,30 @@
+import type { userData } from "../db/user.ts";
+import db from "../db/user.ts";
+import * as log from "https://deno.land/std/log/mod.ts";
+import type { Context } from "https://deno.land/x/abc@master/mod.ts";
+
+export const getCurrentUser = async (c: Context): Promise<userData | undefined> => {
+ const cookies = getUserCookies(c);
+ try {
+ return await db.getUserByVerificationId(cookies.uid, cookies.verification) as userData;
+ } catch (e) {
+ log.error(e);
+ return undefined;
+ }
+}
+
+export const getUserCookies = (c: Context): userCookies => {
+ const uid = parseInt(c.cookies["uid"]);
+ const verification = c.cookies["verification"];
+ return { uid, verification };
+}
+
+export const isAdmin = async (c: Context): Promise<boolean> => {
+ const user = await getCurrentUser(c);
+ return (user && user.isAdmin) as boolean;
+}
+
+export interface userCookies {
+ uid: number;
+ verification: string;
+}
diff --git a/src/views/admin.ejs b/src/views/admin.ejs
new file mode 100644
index 0000000..009b029
--- /dev/null
+++ b/src/views/admin.ejs
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>Admin</title>
+</head>
+<body>
+<% if (initial) { %>
+ <form id="setup-form">
+ <div>
+ <label for="username">Username:</label>
+ <input type="text" name="username" id="username">
+ </div>
+ <div>
+ <label for="email">Email:</label>
+ <input type="email" name="email" id="email">
+ </div>
+ <div>
+ <label for="password">Password:</label>
+ <input type="password" name="password" id="password">
+ </div>
+ <input type="submit" value="Setup">
+ </form>
+ <script src="/js/setup.js"></script>
+<% } else { %>
+ <!-- TODO: Render default admin page -->
+<% } %>
+</body>
+</html>
diff --git a/src/views/index.ejs b/src/views/index.ejs
new file mode 100644
index 0000000..daca490
--- /dev/null
+++ b/src/views/index.ejs
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width"/>
+ <title>Index</title>
+</head>
+<body>
+<% if (files) { %>
+ <%= files %>
+<% } %>
+</body>
+</html>
diff --git a/src/views/index.html b/src/views/index.html
deleted file mode 100644
index 0e3ab11..0000000
--- a/src/views/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width" />
- <title>Index</title>
- </head>
- <body>
- <% if (files) { %> <%= files %>! <% } %>
- </body>
-</html>
diff --git a/src/views/login.ejs b/src/views/login.ejs
new file mode 100644
index 0000000..607d099
--- /dev/null
+++ b/src/views/login.ejs
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>Login</title>
+</head>
+<body>
+<form class="login-form" id="login-form">
+ <div>
+ <label for="username">Username:</label>
+ <input type="text" id="username" placeholder="Username">
+ </div>
+ <div>
+ <label for="password">Password:</label>
+ <input type="password" id="password" placeholder="Password">
+ </div>
+ <input type="submit" value="Login">
+</form>
+<script src="/js/login.js"></script>
+</body>
+</html>
diff --git a/src/views/test.html b/src/views/test.html
deleted file mode 100644
index b188db3..0000000
--- a/src/views/test.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <title>Title</title>
- </head>
- <body>
- <h1>Home :)</h1>
- <% if (name) { %>
- <h1>hello, <%= name %>!</h1>
- <% } %>
- </body>
-</html>