forked from OpenWF/SpaceNinjaServer
		
	feat(webui): handle auth via websocket (#2226)
Now when logging in and out of the game, the webui is notified so it can refresh the nonce, removing the need for constant login requests to revalidate it. Closes #2223 Reviewed-on: OpenWF/SpaceNinjaServer#2226 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									93ef9a5348
								
							
						
					
					
						commit
						2fa6dcc7ed
					
				@ -4,16 +4,16 @@ import { config } from "@/src/services/configService";
 | 
			
		||||
import { buildConfig } from "@/src/services/buildConfigService";
 | 
			
		||||
 | 
			
		||||
import { Account } from "@/src/models/loginModel";
 | 
			
		||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
 | 
			
		||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
 | 
			
		||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { sendWsBroadcastTo } from "@/src/services/webService";
 | 
			
		||||
 | 
			
		||||
export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
			
		||||
 | 
			
		||||
    const account = await Account.findOne({ email: loginRequest.email });
 | 
			
		||||
    const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
 | 
			
		||||
 | 
			
		||||
    const buildLabel: string =
 | 
			
		||||
        typeof request.query.buildLabel == "string"
 | 
			
		||||
@ -42,26 +42,14 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
            loginRequest.ClientType == "webui-register")
 | 
			
		||||
    ) {
 | 
			
		||||
        try {
 | 
			
		||||
            const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
 | 
			
		||||
            let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
 | 
			
		||||
            if (await isNameTaken(name)) {
 | 
			
		||||
                let suffix = 0;
 | 
			
		||||
                do {
 | 
			
		||||
                    ++suffix;
 | 
			
		||||
                    name = nameFromEmail + suffix;
 | 
			
		||||
                } while (await isNameTaken(name));
 | 
			
		||||
            }
 | 
			
		||||
            const name = await getUsernameFromEmail(loginRequest.email);
 | 
			
		||||
            const newAccount = await createAccount({
 | 
			
		||||
                email: loginRequest.email,
 | 
			
		||||
                password: loginRequest.password,
 | 
			
		||||
                DisplayName: name,
 | 
			
		||||
                CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
 | 
			
		||||
                ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
 | 
			
		||||
                CrossPlatformAllowed: true,
 | 
			
		||||
                ForceLogoutVersion: 0,
 | 
			
		||||
                ConsentNeeded: false,
 | 
			
		||||
                TrackedSettings: [],
 | 
			
		||||
                Nonce: nonce,
 | 
			
		||||
                ClientType: loginRequest.ClientType,
 | 
			
		||||
                Nonce: createNonce(),
 | 
			
		||||
                BuildLabel: buildLabel,
 | 
			
		||||
                LastLogin: new Date()
 | 
			
		||||
            });
 | 
			
		||||
@ -80,38 +68,29 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (loginRequest.ClientType == "webui-register") {
 | 
			
		||||
        response.status(400).json({ error: "account already exists" });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isCorrectPassword(loginRequest.password, account.password)) {
 | 
			
		||||
        response.status(400).json({ error: "incorrect login data" });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (loginRequest.ClientType == "webui") {
 | 
			
		||||
        if (!account.Nonce) {
 | 
			
		||||
            account.ClientType = "webui";
 | 
			
		||||
            account.Nonce = nonce;
 | 
			
		||||
    if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
 | 
			
		||||
        // U17 seems to handle "nonce still set" like a login failure.
 | 
			
		||||
        if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
 | 
			
		||||
            response.status(400).send({ error: "nonce still set" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
 | 
			
		||||
            // U17 seems to handle "nonce still set" like a login failure.
 | 
			
		||||
            if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
 | 
			
		||||
                response.status(400).send({ error: "nonce still set" });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        account.ClientType = loginRequest.ClientType;
 | 
			
		||||
        account.Nonce = nonce;
 | 
			
		||||
        account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
 | 
			
		||||
        account.BuildLabel = buildLabel;
 | 
			
		||||
        account.LastLogin = new Date();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    account.ClientType = loginRequest.ClientType;
 | 
			
		||||
    account.Nonce = createNonce();
 | 
			
		||||
    account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
 | 
			
		||||
    account.BuildLabel = buildLabel;
 | 
			
		||||
    account.LastLogin = new Date();
 | 
			
		||||
    await account.save();
 | 
			
		||||
 | 
			
		||||
    // Tell WebUI its nonce has been invalidated
 | 
			
		||||
    sendWsBroadcastTo(account._id.toString(), { logged_out: true });
 | 
			
		||||
 | 
			
		||||
    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { Account } from "@/src/models/loginModel";
 | 
			
		||||
import { sendWsBroadcastTo } from "@/src/services/webService";
 | 
			
		||||
 | 
			
		||||
export const logoutController: RequestHandler = async (req, res) => {
 | 
			
		||||
    if (!req.query.accountId) {
 | 
			
		||||
@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
			
		||||
        throw new Error("Request is missing nonce parameter");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await Account.updateOne(
 | 
			
		||||
    const stat = await Account.updateOne(
 | 
			
		||||
        {
 | 
			
		||||
            _id: req.query.accountId,
 | 
			
		||||
            Nonce: nonce
 | 
			
		||||
@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
			
		||||
            Nonce: 0
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    if (stat.modifiedCount) {
 | 
			
		||||
        // Tell WebUI its nonce has been invalidated
 | 
			
		||||
        sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.writeHead(200, {
 | 
			
		||||
        "Content-Type": "text/html",
 | 
			
		||||
 | 
			
		||||
@ -11,13 +11,13 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
 | 
			
		||||
        email: { type: String, required: true, unique: true },
 | 
			
		||||
        password: { type: String, required: true },
 | 
			
		||||
        DisplayName: { type: String, required: true, unique: true },
 | 
			
		||||
        CountryCode: { type: String, required: true },
 | 
			
		||||
        CountryCode: { type: String, default: "" },
 | 
			
		||||
        ClientType: { type: String },
 | 
			
		||||
        CrossPlatformAllowed: { type: Boolean, required: true },
 | 
			
		||||
        ForceLogoutVersion: { type: Number, required: true },
 | 
			
		||||
        CrossPlatformAllowed: { type: Boolean, default: true },
 | 
			
		||||
        ForceLogoutVersion: { type: Number, default: 0 },
 | 
			
		||||
        AmazonAuthToken: { type: String },
 | 
			
		||||
        AmazonRefreshToken: { type: String },
 | 
			
		||||
        ConsentNeeded: { type: Boolean, required: true },
 | 
			
		||||
        ConsentNeeded: { type: Boolean, default: false },
 | 
			
		||||
        TrackedSettings: { type: [String], default: [] },
 | 
			
		||||
        Nonce: { type: Number, default: 0 },
 | 
			
		||||
        BuildLabel: String,
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,23 @@ export const isNameTaken = async (name: string): Promise<boolean> => {
 | 
			
		||||
    return !!(await Account.findOne({ DisplayName: name }));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createNonce = (): number => {
 | 
			
		||||
    return Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getUsernameFromEmail = async (email: string): Promise<string> => {
 | 
			
		||||
    const nameFromEmail = email.substring(0, email.indexOf("@"));
 | 
			
		||||
    let name = nameFromEmail || email.substring(1) || "SpaceNinja";
 | 
			
		||||
    if (await isNameTaken(name)) {
 | 
			
		||||
        let suffix = 0;
 | 
			
		||||
        do {
 | 
			
		||||
            ++suffix;
 | 
			
		||||
            name = nameFromEmail + suffix;
 | 
			
		||||
        } while (await isNameTaken(name));
 | 
			
		||||
    }
 | 
			
		||||
    return nameFromEmail;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {
 | 
			
		||||
    const account = new Account(accountData);
 | 
			
		||||
    try {
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,10 @@ import { logger } from "../utils/logger";
 | 
			
		||||
import { app } from "../app";
 | 
			
		||||
import { AddressInfo } from "node:net";
 | 
			
		||||
import ws from "ws";
 | 
			
		||||
import { Account } from "../models/loginModel";
 | 
			
		||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
 | 
			
		||||
import { IDatabaseAccountJson } from "../types/loginTypes";
 | 
			
		||||
import { HydratedDocument } from "mongoose";
 | 
			
		||||
 | 
			
		||||
let httpServer: http.Server | undefined;
 | 
			
		||||
let httpsServer: https.Server | undefined;
 | 
			
		||||
@ -25,7 +29,7 @@ export const startWebServer = (): void => {
 | 
			
		||||
    httpServer = http.createServer(app);
 | 
			
		||||
    httpServer.listen(httpPort, () => {
 | 
			
		||||
        wsServer = new ws.Server({ server: httpServer });
 | 
			
		||||
        //wsServer.on("connection", wsOnConnect);
 | 
			
		||||
        wsServer.on("connection", wsOnConnect);
 | 
			
		||||
 | 
			
		||||
        logger.info("HTTP server started on port " + httpPort);
 | 
			
		||||
 | 
			
		||||
@ -33,7 +37,7 @@ export const startWebServer = (): void => {
 | 
			
		||||
        httpsServer = https.createServer(tlsOptions, app);
 | 
			
		||||
        httpsServer.listen(httpsPort, () => {
 | 
			
		||||
            wssServer = new ws.Server({ server: httpsServer });
 | 
			
		||||
            //wssServer.on("connection", wsOnConnect);
 | 
			
		||||
            wssServer.on("connection", wsOnConnect);
 | 
			
		||||
 | 
			
		||||
            logger.info("HTTPS server started on port " + httpsPort);
 | 
			
		||||
 | 
			
		||||
@ -92,11 +96,91 @@ export const stopWebServer = async (): Promise<void> => {
 | 
			
		||||
    await Promise.all(promises);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => {
 | 
			
		||||
    ws.on("message", console.log);
 | 
			
		||||
};*/
 | 
			
		||||
interface IWsCustomData extends ws {
 | 
			
		||||
    accountId?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const sendWsBroadcast = <T>(data: T): void => {
 | 
			
		||||
interface IWsMsgFromClient {
 | 
			
		||||
    auth?: {
 | 
			
		||||
        email: string;
 | 
			
		||||
        password: string;
 | 
			
		||||
        isRegister: boolean;
 | 
			
		||||
    };
 | 
			
		||||
    logout?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IWsMsgToClient {
 | 
			
		||||
    ports?: {
 | 
			
		||||
        http: number | undefined;
 | 
			
		||||
        https: number | undefined;
 | 
			
		||||
    };
 | 
			
		||||
    config_reloaded?: boolean;
 | 
			
		||||
    auth_succ?: {
 | 
			
		||||
        id: string;
 | 
			
		||||
        DisplayName: string;
 | 
			
		||||
        Nonce: number;
 | 
			
		||||
    };
 | 
			
		||||
    auth_fail?: {
 | 
			
		||||
        isRegister: boolean;
 | 
			
		||||
    };
 | 
			
		||||
    logged_out?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
    ws.on("message", async msg => {
 | 
			
		||||
        const data = JSON.parse(String(msg)) as IWsMsgFromClient;
 | 
			
		||||
        if (data.auth) {
 | 
			
		||||
            let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
 | 
			
		||||
            if (account) {
 | 
			
		||||
                if (isCorrectPassword(data.auth.password, account.password)) {
 | 
			
		||||
                    if (!account.Nonce) {
 | 
			
		||||
                        account.ClientType = "webui";
 | 
			
		||||
                        account.Nonce = createNonce();
 | 
			
		||||
                        await (account as HydratedDocument<IDatabaseAccountJson>).save();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    account = null;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (data.auth.isRegister) {
 | 
			
		||||
                const name = await getUsernameFromEmail(data.auth.email);
 | 
			
		||||
                account = await createAccount({
 | 
			
		||||
                    email: data.auth.email,
 | 
			
		||||
                    password: data.auth.password,
 | 
			
		||||
                    ClientType: "webui",
 | 
			
		||||
                    LastLogin: new Date(),
 | 
			
		||||
                    DisplayName: name,
 | 
			
		||||
                    Nonce: createNonce()
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            if (account) {
 | 
			
		||||
                (ws as IWsCustomData).accountId = account.id;
 | 
			
		||||
                ws.send(
 | 
			
		||||
                    JSON.stringify({
 | 
			
		||||
                        auth_succ: {
 | 
			
		||||
                            id: account.id,
 | 
			
		||||
                            DisplayName: account.DisplayName,
 | 
			
		||||
                            Nonce: account.Nonce
 | 
			
		||||
                        }
 | 
			
		||||
                    } satisfies IWsMsgToClient)
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                ws.send(
 | 
			
		||||
                    JSON.stringify({
 | 
			
		||||
                        auth_fail: {
 | 
			
		||||
                            isRegister: data.auth.isRegister
 | 
			
		||||
                        }
 | 
			
		||||
                    } satisfies IWsMsgToClient)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (data.logout) {
 | 
			
		||||
            (ws as IWsCustomData).accountId = undefined;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
 | 
			
		||||
    const msg = JSON.stringify(data);
 | 
			
		||||
    if (wsServer) {
 | 
			
		||||
        for (const client of wsServer.clients) {
 | 
			
		||||
@ -109,3 +193,21 @@ export const sendWsBroadcast = <T>(data: T): void => {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
 | 
			
		||||
    const msg = JSON.stringify(data);
 | 
			
		||||
    if (wsServer) {
 | 
			
		||||
        for (const client of wsServer.clients) {
 | 
			
		||||
            if ((client as IWsCustomData).accountId == accountId) {
 | 
			
		||||
                client.send(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (wssServer) {
 | 
			
		||||
        for (const client of wssServer.clients) {
 | 
			
		||||
            if ((client as IWsCustomData).accountId == accountId) {
 | 
			
		||||
                client.send(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export interface IAccountAndLoginResponseCommons {
 | 
			
		||||
    DisplayName: string;
 | 
			
		||||
    CountryCode: string;
 | 
			
		||||
    CountryCode?: string;
 | 
			
		||||
    ClientType?: string;
 | 
			
		||||
    CrossPlatformAllowed?: boolean;
 | 
			
		||||
    ForceLogoutVersion?: number;
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@
 | 
			
		||||
                <li class="nav-item dropdown user-dropdown">
 | 
			
		||||
                    <button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
 | 
			
		||||
                    <ul class="dropdown-menu dropdown-menu-end">
 | 
			
		||||
                        <li><a class="dropdown-item" href="/webui/" onclick="logout();" data-loc="navbar_logout"></a></li>
 | 
			
		||||
                        <li><a class="dropdown-item" href="/webui/" onclick="doLogout();" data-loc="navbar_logout"></a></li>
 | 
			
		||||
                        <li><hr class="dropdown-divider"></li>
 | 
			
		||||
                        <li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();" data-loc="navbar_renameAccount"></a></li>
 | 
			
		||||
                        <li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();" data-loc="navbar_deleteAccount"></a></li>
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,28 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
 | 
			
		||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
			
		||||
 | 
			
		||||
let auth_pending = false,
 | 
			
		||||
    did_initial_auth = false;
 | 
			
		||||
const sendAuth = isRegister => {
 | 
			
		||||
    if (localStorage.getItem("email") && localStorage.getItem("password")) {
 | 
			
		||||
        auth_pending = true;
 | 
			
		||||
        window.ws.send(
 | 
			
		||||
            JSON.stringify({
 | 
			
		||||
                auth: {
 | 
			
		||||
                    email: localStorage.getItem("email"),
 | 
			
		||||
                    password: wp.encSync(localStorage.getItem("password")),
 | 
			
		||||
                    isRegister
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function openWebSocket() {
 | 
			
		||||
    window.ws = new WebSocket("/custom/ws");
 | 
			
		||||
    window.ws.onopen = () => {
 | 
			
		||||
        sendAuth(false);
 | 
			
		||||
    };
 | 
			
		||||
    window.ws.onmessage = e => {
 | 
			
		||||
        const msg = JSON.parse(e.data);
 | 
			
		||||
        if ("ports" in msg) {
 | 
			
		||||
@ -21,31 +41,9 @@ function openWebSocket() {
 | 
			
		||||
                single.loadRoute("/webui/cheats");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    window.ws.onclose = function () {
 | 
			
		||||
        setTimeout(openWebSocket, 3000);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
openWebSocket();
 | 
			
		||||
 | 
			
		||||
let loginOrRegisterPending = false;
 | 
			
		||||
window.registerSubmit = false;
 | 
			
		||||
 | 
			
		||||
function doLogin() {
 | 
			
		||||
    if (loginOrRegisterPending) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    loginOrRegisterPending = true;
 | 
			
		||||
    localStorage.setItem("email", $("#email").val());
 | 
			
		||||
    localStorage.setItem("password", $("#password").val());
 | 
			
		||||
    loginFromLocalStorage();
 | 
			
		||||
    registerSubmit = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loginFromLocalStorage() {
 | 
			
		||||
    const isRegister = registerSubmit;
 | 
			
		||||
    doLoginRequest(
 | 
			
		||||
        data => {
 | 
			
		||||
        if ("auth_succ" in msg) {
 | 
			
		||||
            auth_pending = false;
 | 
			
		||||
            const data = msg.auth_succ;
 | 
			
		||||
            if (single.getCurrentPath() == "/webui/") {
 | 
			
		||||
                single.loadRoute("/webui/inventory");
 | 
			
		||||
            }
 | 
			
		||||
@ -55,55 +53,74 @@ function loginFromLocalStorage() {
 | 
			
		||||
            if (window.dict) {
 | 
			
		||||
                updateLocElements();
 | 
			
		||||
            }
 | 
			
		||||
            updateInventory();
 | 
			
		||||
        },
 | 
			
		||||
        () => {
 | 
			
		||||
            logout();
 | 
			
		||||
            alert(loc(isRegister ? "code_regFail" : "code_loginFail"));
 | 
			
		||||
            if (!did_initial_auth) {
 | 
			
		||||
                did_initial_auth = true;
 | 
			
		||||
                updateInventory();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
        if ("auth_fail" in msg) {
 | 
			
		||||
            auth_pending = false;
 | 
			
		||||
            logout();
 | 
			
		||||
            if (single.getCurrentPath() == "/webui/") {
 | 
			
		||||
                alert(loc(msg.auth_fail.isRegister ? "code_regFail" : "code_loginFail"));
 | 
			
		||||
            } else {
 | 
			
		||||
                single.loadRoute("/webui/");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ("logged_out" in msg) {
 | 
			
		||||
            sendAuth();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    window.ws.onclose = function () {
 | 
			
		||||
        window.ws = undefined;
 | 
			
		||||
        setTimeout(openWebSocket, 3000);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
openWebSocket();
 | 
			
		||||
 | 
			
		||||
function getWebSocket() {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
        let interval;
 | 
			
		||||
        interval = setInterval(() => {
 | 
			
		||||
            if (window.ws) {
 | 
			
		||||
                clearInterval(interval);
 | 
			
		||||
                resolve(window.ws);
 | 
			
		||||
            }
 | 
			
		||||
        }, 10);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function doLoginRequest(succ_cb, fail_cb) {
 | 
			
		||||
    const req = $.post({
 | 
			
		||||
        url: "/api/login.php",
 | 
			
		||||
        contentType: "text/plain",
 | 
			
		||||
        data: JSON.stringify({
 | 
			
		||||
            email: localStorage.getItem("email").toLowerCase(),
 | 
			
		||||
            password: wp.encSync(localStorage.getItem("password"), "hex"),
 | 
			
		||||
            time: parseInt(new Date() / 1000),
 | 
			
		||||
            s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind
 | 
			
		||||
            lang: "en",
 | 
			
		||||
            // eslint-disable-next-line no-loss-of-precision
 | 
			
		||||
            date: 1501230947855458660, // ???
 | 
			
		||||
            ClientType: registerSubmit ? "webui-register" : "webui",
 | 
			
		||||
            PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data
 | 
			
		||||
        })
 | 
			
		||||
    });
 | 
			
		||||
    req.done(succ_cb);
 | 
			
		||||
    req.fail(fail_cb);
 | 
			
		||||
    req.always(() => {
 | 
			
		||||
        loginOrRegisterPending = false;
 | 
			
		||||
    });
 | 
			
		||||
window.registerSubmit = false;
 | 
			
		||||
 | 
			
		||||
function doLogin() {
 | 
			
		||||
    if (auth_pending) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    localStorage.setItem("email", $("#email").val());
 | 
			
		||||
    localStorage.setItem("password", $("#password").val());
 | 
			
		||||
    sendAuth(registerSubmit);
 | 
			
		||||
    window.registerSubmit = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function revalidateAuthz(succ_cb) {
 | 
			
		||||
    return doLoginRequest(
 | 
			
		||||
        data => {
 | 
			
		||||
            window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce;
 | 
			
		||||
            succ_cb();
 | 
			
		||||
        },
 | 
			
		||||
        () => {
 | 
			
		||||
            logout();
 | 
			
		||||
            alert(loc("code_nonValidAuthz"));
 | 
			
		||||
            single.loadRoute("/webui/"); // Show login screen
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    getWebSocket().then(() => {
 | 
			
		||||
        // We have a websocket connection, so authz should be good.
 | 
			
		||||
        succ_cb();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function logout() {
 | 
			
		||||
    localStorage.removeItem("email");
 | 
			
		||||
    localStorage.removeItem("password");
 | 
			
		||||
    did_initial_auth = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function doLogout() {
 | 
			
		||||
    logout();
 | 
			
		||||
    if (window.ws) {
 | 
			
		||||
        // Unsubscribe from notifications about nonce invalidation
 | 
			
		||||
        window.ws.send(JSON.stringify({ logout: true }));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renameAccount() {
 | 
			
		||||
@ -129,10 +146,6 @@ function deleteAccount() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (localStorage.getItem("email") && localStorage.getItem("password")) {
 | 
			
		||||
    loginFromLocalStorage();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
single.on("route_load", function (event) {
 | 
			
		||||
    if (event.route.paths[0] != "/webui/") {
 | 
			
		||||
        // Authorised route?
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user