diff options
-rw-r--r-- | .editorconfig | 9 | ||||
-rw-r--r-- | src/db/DBController.ts | 10 | ||||
-rw-r--r-- | src/db/tables.sql | 30 | ||||
-rw-r--r-- | src/db/user.ts | 76 | ||||
-rw-r--r-- | src/groups/user.ts | 7 | ||||
-rw-r--r-- | src/handler/admin.ts | 12 | ||||
-rw-r--r-- | src/handler/fileView.ts | 6 | ||||
-rw-r--r-- | src/handler/user.ts | 96 | ||||
-rw-r--r-- | src/main.ts | 21 | ||||
-rw-r--r-- | src/public/css/style.css | 8 | ||||
-rw-r--r-- | src/public/fonts/ShareTechMono.ttf | bin | 0 -> 42756 bytes | |||
-rw-r--r-- | src/public/index.html | 11 | ||||
-rw-r--r-- | src/public/js/login.js | 18 | ||||
-rw-r--r-- | src/public/js/setup.js | 21 | ||||
-rw-r--r-- | src/public/script.js | 0 | ||||
-rw-r--r-- | src/public/style.css | 0 | ||||
-rw-r--r-- | src/public/test.html | 13 | ||||
-rw-r--r-- | src/util/files.ts | 28 | ||||
-rw-r--r-- | src/util/server.ts | 14 | ||||
-rw-r--r-- | src/util/user.ts | 30 | ||||
-rw-r--r-- | src/views/admin.ejs | 32 | ||||
-rw-r--r-- | src/views/index.ejs | 13 | ||||
-rw-r--r-- | src/views/index.html | 11 | ||||
-rw-r--r-- | src/views/login.ejs | 24 | ||||
-rw-r--r-- | src/views/test.html | 13 |
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 Binary files differnew file mode 100644 index 0000000..c8e530f --- /dev/null +++ b/src/public/fonts/ShareTechMono.ttf 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> |