rewrite to use account based auth

This commit is contained in:
Holly Stubbs 2024-04-25 02:37:37 +01:00
parent 0c27648d3b
commit 8507ca0840
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
6 changed files with 141 additions and 39 deletions

View file

@ -49,7 +49,8 @@ if (!window.TE_ACTIVE) {
ClientJoined: 3,
Clients: 4,
ClientLeft: 5,
Ping: 6
Ping: 6,
GroupData: 7
};
const styles = document.createElement("style");
@ -211,12 +212,13 @@ if (!window.TE_ACTIVE) {
const gotoButton = document.createElement("button");
gotoButton.innerText = "G";
gotoButton.onclick = () => window.location.href = location;
buttonBox.appendChild(gotoButton);
groupUsers.appendChild(user);
}
let clientWidth = document.body.getBoundingClientRect().width
let clientWidth = document.body.getBoundingClientRect().width;
setInterval(() => {
if (document.body.scrollHeight > window.innerHeight) {
@ -237,10 +239,6 @@ if (!window.TE_ACTIVE) {
let oldMouseX = 0;
let oldMouseY = 0;
let lastSendTime = 0;
let username = "";
if (localStorage["t00mp_username"]) {
username = localStorage["t00mp_username"];
}
class RemoteClient {
constructor(name) {
@ -398,18 +396,23 @@ if (!window.TE_ACTIVE) {
}
animate();
function doConnect() {
function doConnect(apiKey) {
const Buffer = getBufferClass();
ws = new WebSocket(window.location.href.includes("//localhost:") ? "ws://localhost:38195" : "wss://ws.eusv.net/t00mp");
let keepAliveInterval;
ws.onopen = () => {
selfCursor = new RemoteClient(username);
otherCursors.innerHTML = "";
selfCursor = new RemoteClient(localStorage["t00mp_username"]);
selfCursor.probeImage.style.visibility = "hidden";
selfCursor.element.style.visibility = localStorage["t00mp_cursorStyle"] ?? "hidden";
selfCursor.hasBeenMoved = true;
const currentPage = window.location.href.split("/").slice(3).join("/");
ws.send(createWriter(Endian.LE, 4 + username.length + currentPage.length).writeByte(MessageType.ClientDetails).writeShortString(username).writeString(currentPage).toBuffer());
ws.send(createWriter(Endian.LE, 4 + apiKey.length + currentPage.length)
.writeByte(MessageType.ClientDetails)
.writeShortString(apiKey)
.writeString(currentPage)
.toBuffer());
keepAliveInterval = setInterval(() => {
ws.send(keepAlivePacket);
}, 5000);
@ -473,22 +476,22 @@ if (!window.TE_ACTIVE) {
}
ws = undefined;
ready = false;
setTimeout(doConnect, 5000);
setTimeout(() => doConnect(localStorage["mpapikey"]), 5000);
}
ws.onclose = onCloseAndError;
ws.onerror = onCloseAndError;
}
function createOnlineDialog() {
function createLoginDialog() {
const bg = document.createElement("div");
bg.style = "z-index:1000000000;position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5)";
const dialog = document.createElement("div");
dialog.style = "position:absolute;top:50%;left:50%;width:15rem;height:9rem;background-color:#343434;transform:translate(-50%,-50%);text-align:center;color:white";
dialog.style = "position:absolute;top:50%;left:50%;width:15rem;background-color:#343434;padding-bottom:1rem;transform:translate(-50%,-50%);text-align:center;color:white";
bg.appendChild(dialog);
const title = document.createElement("h4");
title.innerText = "T00 MultiUser";
title.innerText = "MultiProbe";
dialog.appendChild(title);
const submitFunction = (event) => {
/*const submitFunction = (event) => {
// Jank
if (event && event.keyCode !== 13) {
return;
@ -502,24 +505,32 @@ if (!window.TE_ACTIVE) {
localStorage["t00mp_username"] = username.value;
bg.remove();
window.location.href = window.location.href;
};
};*/
const loginForm = document.createElement("form");
dialog.appendChild(loginForm);
const username = document.createElement("input");
username.placeholder = "Enter Username";
username.maxLength = 32;
username.style.width = "12rem";
username.onkeypress = submitFunction;
if (localStorage["t00mp_username"]) {
username.value = localStorage["t00mp_username"];
}
dialog.appendChild(username);
username.name = "username";
//username.onkeypress = submitFunction;
loginForm.appendChild(username);
const password = document.createElement("input");
password.type = "password";
password.style.marginTop = ".5rem";
password.placeholder = "Enter Password";
password.style.width = "12rem";
password.name = "password";
loginForm.appendChild(password);
const buttons = document.createElement("div");
buttons.style.marginTop = "1rem";
dialog.appendChild(buttons);
loginForm.appendChild(buttons);
const submitButton = document.createElement("button");
submitButton.innerText = (!localStorage["t00mp_username"] || localStorage["t00mp_username"] === "") ? "Connect" : "Change Username";
submitButton.onclick = () => submitFunction(null);
submitButton.innerText = "Connect";
submitButton.type = "submit";
//submitButton.onclick = () => submitFunction(null);
buttons.appendChild(submitButton);
if (localStorage["t00mp_username"] !== "") {
/*if (localStorage["t00mp_username"] !== "") {
const disconnectButton = document.createElement("button");
disconnectButton.innerText = "Disconnect";
disconnectButton.onclick = () => {
@ -528,22 +539,63 @@ if (!window.TE_ACTIVE) {
window.location.href = window.location.href;
}
buttons.appendChild(disconnectButton);
}
}*/
const doNotButton = document.createElement("button");
doNotButton.innerText = "Close";
doNotButton.onclick = () => bg.remove();
doNotButton.style.marginLeft = "1rem";
buttons.appendChild(doNotButton);
loginForm.onsubmit = (e) => {
e.preventDefault();
username.disabled = true;
password.disabled = true;
submitButton.disabled = true;
doNotButton.disabled = true;
fetch(window.location.href.replace("127.0.0.1", "localhost").includes("//localhost:") ? "http://localhost:38194/api/login" : "https://multiprobe.eusv.net/api/login", {
method: "POST",
body: `username=${encodeURIComponent(username.value)}&password=${encodeURIComponent(password.value)}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).then(res => {
if (res.status === 200) {
res.text().then(apiKey => {
localStorage["t00mp_username"] = username.value;
localStorage["mpapikey"] = apiKey;
doConnect(apiKey);
});
} else {
username.disabled = false;
password.disabled = false;
submitButton.disabled = false;
doNotButton.disabled = false;
}
}).catch(err => {
console.error(err);
username.disabled = false;
password.disabled = false;
submitButton.disabled = false;
doNotButton.disabled = false;
});
}
document.body.appendChild(bg);
}
const openMenuButton = document.createElement("button");
openMenuButton.style = "opacity:0.25;position:fixed;top:0px;right:0px;z-index:9999999;margin:4px;background-color:black;color:white;border:1px solid white";
openMenuButton.innerText = "MultiUser Menu";
openMenuButton.onclick = () => createOnlineDialog();
openMenuButton.innerText = "MultiProbe Menu";
openMenuButton.onclick = () => createLoginDialog();
document.body.appendChild(openMenuButton);
if (username !== "") {
doConnect();
if (localStorage["mpapikey"] && localStorage["mpapikey"] !== "") {
doConnect(localStorage["mpapikey"]);
}
/*if (username !== "") {
doConnect();
}*/
})();

View file

@ -5,5 +5,6 @@ export enum MessageType {
ClientJoined,
Clients,
ClientLeft,
Ping
Ping,
GroupData
}

View file

@ -271,6 +271,26 @@ fastify.post("/party/join", async (req, res) => {
}
});
// API
fastify.post("/api/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.status(401).send("Username or Password incorrect");
}
const user = await UserService.GetUserByUsername(data.username);
if (!user) {
return res.status(401).send("Username or Password incorrect");
}
if (await PasswordUtility.ValidatePassword(user.PasswordHash, user.PasswordSalt, data.password)) {
return res.status(200).send(user.APIKey);
}
return res.status(401).send("Username or Password incorrect");
});
// Websocket stuff
const websocketServer = new WebSocketServer({
@ -331,7 +351,12 @@ websocketServer.on("connection", (socket) => {
if (user !== undefined) {
return;
}
const username = reader.readShortString();
const apiKey = reader.readShortString();
const dbUser = await UserService.GetUserByAPIKey(apiKey);
if (dbUser == null) {
return;
}
const rawURL = reader.readString();
let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", "");
if (page === "index") {
@ -349,8 +374,8 @@ websocketServer.on("connection", (socket) => {
for (const otherUser of usersOnPage) {
usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY);
}
user = users.set(myUUID, new RemoteUser(socket, username, page, rawURL));
sendToAllButSelf(user, createWriter(Endian.LE, 6 + username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(username).toBuffer());
user = users.set(myUUID, new RemoteUser(socket, dbUser.Username, page, rawURL));
sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer());
user.send(usersToSend.toBuffer());
break;
case MessageType.CursorPos:

View file

@ -3,6 +3,7 @@ export default class User {
public Username:string;
public PasswordSalt:string;
public PasswordHash:string;
public APIKey:string;
public CreatedByUserId:number;
public CreatedDatetime:Date;
public LastModifiedByUserId?:number;
@ -11,12 +12,13 @@ export default class User {
public DeletedDatetime?:Date;
public IsDeleted:boolean;
public constructor(id?:number, username?:string, passwordSalt?:string, passwordHash?:string, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) {
if (typeof(id) == "number" && typeof(username) == "string" && typeof(passwordHash) == "string" && typeof(passwordSalt) == "string" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") {
public constructor(id?:number, username?:string, passwordSalt?:string, passwordHash?:string, apiKey?:string, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) {
if (typeof(id) == "number" && typeof(username) == "string" && typeof(passwordHash) == "string" && typeof(passwordSalt) == "string" && typeof(apiKey) == "string" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") {
this.Id = id;
this.Username = username;
this.PasswordHash = passwordHash;
this.PasswordSalt = passwordSalt;
this.APIKey = apiKey;
this.CreatedByUserId = createdByUserId;
this.CreatedDatetime = createdDateTime;
this.LastModifiedByUserId = lastModifiedByUserId;
@ -29,6 +31,7 @@ export default class User {
this.Username = "";
this.PasswordHash = "";
this.PasswordSalt = "";
this.APIKey = "";
this.CreatedByUserId = Number.MIN_VALUE;
this.CreatedDatetime = new Date(0);
this.IsDeleted = false;

View file

@ -25,14 +25,25 @@ export default class UserRepo {
}
}
public static async selectByAPIKey(apiKey:string) {
const dbUser = await Database.Instance.query("SELECT * FROM User WHERE APIKey = ? LIMIT 1", [apiKey]);
if (dbUser == null || dbUser.length === 0) {
return null;
} else {
const user = new User();
populateUserFromDB(user, dbUser[0]);
return user;
}
}
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 ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
await Database.Instance.query("INSERT User (Username, PasswordHash, PasswordSalt, APIKey, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, 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 ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
await Database.Instance.query(`UPDATE User SET Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [
user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
]);
}
}
@ -43,6 +54,7 @@ function populateUserFromDB(user:User, dbUser:any) {
user.Username = dbUser.Username;
user.PasswordHash = dbUser.PasswordHash;
user.PasswordSalt = dbUser.PasswordSalt;
user.APIKey = dbUser.APIKey;
user.CreatedByUserId = dbUser.CreatedByUserId;
user.CreatedDatetime = dbUser.CreatedDatetime;
user.LastModifiedByUserId = dbUser.LastModifiedByUserId;

View file

@ -26,6 +26,15 @@ export default class UserService {
}
}
public static async GetUserByAPIKey(apiKey:string) {
try {
return await UserRepo.selectByAPIKey(apiKey);
} catch (e) {
Console.printError(`MultiProbe server service error:\n${e}`);
throw e;
}
}
public static async GetUserParties(userId:number) {
try {
return await PartyRepo.selectByUserId(userId);