start work on logged in pages + add auth
This commit is contained in:
parent
88e92051e3
commit
1183f0f9b6
17 changed files with 300 additions and 23 deletions
|
@ -3,6 +3,11 @@
|
|||
"http": 38194,
|
||||
"ws": 39195
|
||||
},
|
||||
"session": {
|
||||
"validity": 86400,
|
||||
"length": 64,
|
||||
"secret": "changeme"
|
||||
},
|
||||
"database": {
|
||||
"address": "localhost",
|
||||
"port": 3306,
|
||||
|
|
117
server/index.ts
117
server/index.ts
|
@ -1,6 +1,8 @@
|
|||
import { createReader, createWriter, Endian } from "bufferstuff";
|
||||
import { WebSocketServer } from "ws";
|
||||
import Fastify from "fastify";
|
||||
import FastifyFormBody from "@fastify/formbody";
|
||||
import FastifyCookie from "@fastify/cookie";
|
||||
import FastifyView from "@fastify/view";
|
||||
import EJS from "ejs";
|
||||
import Config from "./objects/Config";
|
||||
|
@ -9,6 +11,11 @@ import RemoteUser from "./objects/RemoteUser";
|
|||
import { MessageType } from "./enums/MessageType";
|
||||
import Database from "./objects/Database";
|
||||
import { Console } from "hsconsole";
|
||||
import UserService from "./services/UserService";
|
||||
import UsernameData from "./interfaces/UsernameData";
|
||||
import { randomBytes } from "crypto";
|
||||
import SessionUser from "./objects/SessionUser";
|
||||
import PasswordUtility from "./utilities/PasswordUtility";
|
||||
|
||||
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
||||
|
||||
|
@ -18,6 +25,17 @@ new Database(Config.database.address, Config.database.port, Config.database.user
|
|||
|
||||
// Web stuff
|
||||
|
||||
const sessions = new FunkyArray<string, SessionUser>();
|
||||
const sessionExpiryInterval = setInterval(() => {
|
||||
const currentTime = Date.now();
|
||||
for (const key of sessions.keys) {
|
||||
const session = sessions.get(key);
|
||||
if (!session || (session && currentTime >= session.validityPeriod.getTime())) {
|
||||
sessions.remove(key);
|
||||
}
|
||||
}
|
||||
}, 3600000);
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: false
|
||||
});
|
||||
|
@ -26,9 +44,43 @@ fastify.register(FastifyView, {
|
|||
engine: {
|
||||
ejs: EJS
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
fastify.register(FastifyFormBody);
|
||||
|
||||
fastify.register(FastifyCookie, {
|
||||
secret: Config.session.secret,
|
||||
parseOptions: {
|
||||
path: "/",
|
||||
secure: true
|
||||
}
|
||||
});
|
||||
|
||||
// Get Methods
|
||||
|
||||
function validateSession(cookies:{ [cookieName: string]: string | undefined }) {
|
||||
if ("MP_SESSION" in cookies && typeof(cookies["MP_SESSION"]) === "string") {
|
||||
const key = FastifyCookie.unsign(cookies["MP_SESSION"], Config.session.secret);
|
||||
if (key.valid && sessions.has(key.value ?? "badkey")) {
|
||||
return sessions.get(key.value ?? "badkey");
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
fastify.get("/", async (req, res) => {
|
||||
let session:SessionUser | undefined;
|
||||
if (session = validateSession(req.cookies)) {
|
||||
const user = await UserService.GetUser(session.userId);
|
||||
//const groups = await UserService.GetUserParties(session.userId);
|
||||
if (user) {
|
||||
return res.view("templates/home.ejs", { user, parties: [] });
|
||||
}
|
||||
|
||||
return res.view("templates/index.ejs", { });
|
||||
}
|
||||
|
||||
return res.view("templates/index.ejs", { });
|
||||
});
|
||||
|
||||
|
@ -45,7 +97,66 @@ fastify.get("/account/register", async (req, res) => {
|
|||
});
|
||||
|
||||
fastify.setNotFoundHandler(async (req, res) => {
|
||||
return res.view("templates/404.ejs", { });
|
||||
return res.status(404).view("templates/404.ejs", { });
|
||||
});
|
||||
|
||||
// Post Methods
|
||||
|
||||
fastify.post("/account/register", async (req, res) => {
|
||||
const data = req.body as UsernameData;
|
||||
if (typeof(data.username) !== "string" || typeof(data.password) !== "string" || data.username.length > 32 || data.password.length < 8) {
|
||||
return res.view("templates/account/register.ejs", { });
|
||||
}
|
||||
|
||||
const username = data.username.replaceAll("<", "<").replaceAll(">", ">");
|
||||
await UserService.CreateUser(0, username, data.password);
|
||||
|
||||
const user = await UserService.GetUserByUsername(username);
|
||||
if (!user) {
|
||||
return res.view("templates/account/register.ejs", { });
|
||||
}
|
||||
|
||||
const validPeriod = new Date();
|
||||
validPeriod.setTime(validPeriod.getTime() + Config.session.validity);
|
||||
const key = randomBytes(Config.session.length).toString("hex");
|
||||
|
||||
sessions.set(key, new SessionUser(user.Id, validPeriod));
|
||||
|
||||
res.setCookie("MP_SESSION", key, {
|
||||
path: "/",
|
||||
signed: true
|
||||
});
|
||||
|
||||
return res.redirect(302, "/");
|
||||
});
|
||||
|
||||
fastify.post("/account/login", async (req, res) => {
|
||||
const data = req.body as UsernameData;
|
||||
if (typeof(data.username) !== "string" || typeof(data.password) !== "string" || data.username.length > 32 || data.password.length < 8) {
|
||||
return res.view("templates/account/login.ejs", { });
|
||||
}
|
||||
|
||||
const user = await UserService.GetUserByUsername(data.username);
|
||||
if (!user) {
|
||||
return res.view("templates/account/login.ejs", { });
|
||||
}
|
||||
|
||||
if (await PasswordUtility.ValidatePassword(user.PasswordHash, user.PasswordSalt, data.password)) {
|
||||
const validPeriod = new Date();
|
||||
validPeriod.setTime(validPeriod.getTime() + Config.session.validity);
|
||||
const key = randomBytes(Config.session.length).toString("hex");
|
||||
|
||||
sessions.set(key, new SessionUser(user.Id, validPeriod));
|
||||
|
||||
res.setCookie("MP_SESSION", key, {
|
||||
path: "/",
|
||||
signed: true
|
||||
});
|
||||
|
||||
return res.redirect(302, "/");
|
||||
}
|
||||
|
||||
return res.view("templates/account/login.ejs", { });
|
||||
});
|
||||
|
||||
// Websocket stuff
|
||||
|
@ -175,6 +286,8 @@ function shutdown() {
|
|||
Console.printInfo("Shutting down...");
|
||||
websocketServer.close(async () => {
|
||||
await fastify.close();
|
||||
clearInterval(sessionExpiryInterval);
|
||||
|
||||
Console.cleanup();
|
||||
console.log("Goodbye!");
|
||||
});
|
||||
|
|
4
server/interfaces/UsernameData.ts
Normal file
4
server/interfaces/UsernameData.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export default interface UsernameData {
|
||||
username?: string,
|
||||
password?: string
|
||||
}
|
|
@ -6,6 +6,7 @@ export default class Config {
|
|||
public constructor() { throw new Error("Static Class"); }
|
||||
|
||||
public static ports:ConfigPorts = config.ports;
|
||||
public static session:ConfigSession = config.session;
|
||||
public static database:ConfigDatabase = config.database;
|
||||
}
|
||||
|
||||
|
@ -14,6 +15,12 @@ interface ConfigPorts {
|
|||
ws: number
|
||||
}
|
||||
|
||||
interface ConfigSession {
|
||||
validity: number,
|
||||
length: number,
|
||||
secret: string
|
||||
}
|
||||
|
||||
interface ConfigDatabase {
|
||||
address: string,
|
||||
port: number,
|
||||
|
|
9
server/objects/SessionUser.ts
Normal file
9
server/objects/SessionUser.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export default class SessionUser {
|
||||
public readonly userId:number;
|
||||
public readonly validityPeriod:Date;
|
||||
|
||||
constructor(userId:number, validityPeriod:Date) {
|
||||
this.userId = userId;
|
||||
this.validityPeriod = validityPeriod;
|
||||
}
|
||||
}
|
28
server/package-lock.json
generated
28
server/package-lock.json
generated
|
@ -9,6 +9,8 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^9.3.1",
|
||||
"@fastify/formbody": "^7.4.0",
|
||||
"@fastify/view": "^9.0.0",
|
||||
"bufferstuff": "^1.5.1",
|
||||
"ejs": "^3.1.10",
|
||||
|
@ -52,6 +54,15 @@
|
|||
"fast-uri": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/cookie": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.3.1.tgz",
|
||||
"integrity": "sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==",
|
||||
"dependencies": {
|
||||
"cookie-signature": "^1.1.0",
|
||||
"fastify-plugin": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/error": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz",
|
||||
|
@ -65,6 +76,15 @@
|
|||
"fast-json-stringify": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/formbody": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-7.4.0.tgz",
|
||||
"integrity": "sha512-H3C6h1GN56/SMrZS8N2vCT2cZr7mIHzBHzOBa5OPpjfB/D6FzP9mMpE02ZzrFX0ANeh0BAJdoXKOF2e7IbV+Og==",
|
||||
"dependencies": {
|
||||
"fast-querystring": "^1.0.0",
|
||||
"fastify-plugin": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/merge-json-schemas": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz",
|
||||
|
@ -598,6 +618,14 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz",
|
||||
"integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"author": "tgpholly",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^9.3.1",
|
||||
"@fastify/formbody": "^7.4.0",
|
||||
"@fastify/view": "^9.0.0",
|
||||
"bufferstuff": "^1.5.1",
|
||||
"ejs": "^3.1.10",
|
||||
|
|
|
@ -29,11 +29,11 @@ export default class PartyRepo {
|
|||
public static async insertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
await Database.Instance.query("INSERT Party (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted)
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||
]);
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE Party SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted), user.Id
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ export default class UserRepo {
|
|||
public static async insertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted)
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||
]);
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId, user.LastModifiedDatetime?.getTime(), user.DeletedByUserId, user.DeletedDatetime?.getTime(), Number(user.IsDeleted), user.Id
|
||||
user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,14 @@ export default class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
public static async GetUserByUsername(username:string) {
|
||||
try {
|
||||
return await UserRepo.selectByUsername(username);
|
||||
} catch (e) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static async GetParty(id:number) {
|
||||
try {
|
||||
return await PartyRepo.selectById(id);
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<%- include("base/header") -%>
|
||||
<%- include("base/header", { title: "404"}) %>
|
||||
<h1 class="text-center" style="font-size:10rem">404!</h1>
|
||||
<%- include("base/footer") -%>
|
||||
<%- include("base/footer") %>
|
|
@ -1,22 +1,27 @@
|
|||
<%- include("../base/header") -%>
|
||||
<%- include("../base/header", { title: "Login" }) %>
|
||||
<h1 class="text-center mb-5">Login</h1>
|
||||
<form>
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
<div class="col-4"></div>
|
||||
<div class="col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Username</label>
|
||||
<input class="form-control" type="text" name="u" maxlength="32" required />
|
||||
<input class="form-control" type="text" name="username" maxlength="32" required />
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input class="form-control" type="password" name="p" required />
|
||||
<input class="form-control" type="password" name="password" required />
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<input class="btn btn-primary" type="submit" value="Login" />
|
||||
<a href="/account/register">I don't have an account.</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<input class="btn btn-primary" type="submit" value="Continue" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</form>
|
||||
<%- include("../base/footer") -%>
|
||||
<%- include("../base/footer") %>
|
27
server/templates/account/register.ejs
Normal file
27
server/templates/account/register.ejs
Normal file
|
@ -0,0 +1,27 @@
|
|||
<%- include("../base/header", { title: "Register" }) %>
|
||||
<h1 class="text-center mb-5">Register</h1>
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
<div class="col-4"></div>
|
||||
<div class="col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Username</label>
|
||||
<input class="form-control" type="text" name="username" maxlength="32" required />
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input class="form-control" type="password" name="password" minlength="8" autocomplete="new-password" required />
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/account/login">I already have an account!</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<input class="btn btn-primary" type="submit" value="Continue" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</form>
|
||||
<%- include("../base/footer") %>
|
|
@ -1,4 +1,22 @@
|
|||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js" integrity="sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent/3.1.1/cookieconsent.min.js" integrity="sha512-yXXqOFjdjHNH1GND+1EO0jbvvebABpzGKD66djnUfiKlYME5HGMUJHoCaeE4D5PTG2YsSJf6dwqyUUvQvS0vaA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
window.cookieconsent.initialise({
|
||||
"palette": {
|
||||
"popup": {
|
||||
"background": "#0b5ed7",
|
||||
"text": "#fff"
|
||||
},
|
||||
"button": {
|
||||
"background": "#198754",
|
||||
"text": "#fff"
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"message": "This site uses cookies to retain your login, no more, no less."
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -3,16 +3,19 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<title><%= title %> - MultiProbe</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent/3.1.1/cookieconsent.min.css" integrity="sha512-LQ97camar/lOliT/MqjcQs5kWgy6Qz/cCRzzRzUCfv0fotsCTC9ZHXaPQmJV8Xu/PVALfJZ7BDezl5lW3/qBxg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">MultiProbe</a>
|
||||
<a class="navbar-brand" href="/">
|
||||
MultiProbe
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
@ -20,7 +23,20 @@
|
|||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
|
||||
</ul>
|
||||
<a class="nav-link active" aria-current="page" href="/account/login">Login</a>
|
||||
<ul class="navbar-nav">
|
||||
<% if (typeof(userId) !== "undefined") { %>
|
||||
<div class="nav-item float-end">
|
||||
<a class="nav-link" href="/account/logout">Logout</a>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="nav-item float-end">
|
||||
<a class="nav-link" href="/account/login">Login</a>
|
||||
</div>
|
||||
<div class="nav-item float-end">
|
||||
<a class="nav-link" href="/account/register">Register</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -1,3 +1,33 @@
|
|||
<%- include("base/header") -%>
|
||||
<h1>Welcome back USER!</h1>
|
||||
<%- include("base/footer") -%>
|
||||
<%- include("base/header", { title: "Home", userId: user.Id }) %>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>Welcome back <%= user.Username %>!</h1>
|
||||
<h3>What would you like to do?</h3>
|
||||
<div class="mt-3">
|
||||
<div>
|
||||
<a class="btn btn-primary btn-lg me-2" href="/account/username">Change Username</a>
|
||||
<a class="btn btn-primary btn-lg me-2" href="/account/password">Change Password</a>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-primary btn-lg me-2" href="/account/password">Create Party</a>
|
||||
<a class="btn btn-primary btn-lg me-2" href="/account/username">Join Party</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Your Parties</h3>
|
||||
<% if (parties.length > 0) { %>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<div class="alert alert-primary" role="alert">You are not in any parties.</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<%- include("base/footer") %>
|
|
@ -1,3 +1,8 @@
|
|||
<%- include("base/header") -%>
|
||||
<h1>Hello world!</h1>
|
||||
<%- include("base/footer") -%>
|
||||
<%- include("base/header", { title: "Home" }) %>
|
||||
<h1><b>MultiProbe</b></h1>
|
||||
<h3>A way to explore <a href="https://angusnicneven.com">Terminal 00</a> with friends.</h3>
|
||||
<div class="mt-3">
|
||||
<a type="button" class="btn btn-primary btn-lg me-2" href="/account/register">Register</a>
|
||||
<a type="button" class="btn btn-secondary btn-lg" href="/account/login">Login</a>
|
||||
</div>
|
||||
<%- include("base/footer") %>
|
Loading…
Reference in a new issue