Implement local server for OTP authentication and enhance socket client functionality
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good
Add a LocalServer class to handle OTP authentication requests and server status checks. Update index.js to integrate local server functionality, including checks for server status and OTP authentication flow. Enhance SocketClient with connection waiting and improved OTP authentication handling. Update config.json with a new authCode for the host.
This commit is contained in:
parent
55b4c1a42e
commit
2e95075d3e
@ -5,7 +5,7 @@
|
|||||||
"apiUrl": "https://dev.tombutcher.work/api",
|
"apiUrl": "https://dev.tombutcher.work/api",
|
||||||
"host": {
|
"host": {
|
||||||
"id": "691a1db49ce913faf0e51284",
|
"id": "691a1db49ce913faf0e51284",
|
||||||
"authCode": "9uu3DC0si__-F9FnGWTKudle5z6yZasFMlKohnShElPekRYteh-LlZaksHOXFfOO"
|
"authCode": "BNj13heKj9zUZIJ0IodSU-V-OSZHNJGAA-nLa4nwFrDHa6N1wah7mpw_7pqbT9Il"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
|
|||||||
94
src/index.js
94
src/index.js
@ -1,7 +1,9 @@
|
|||||||
|
import axios from "axios";
|
||||||
import { loadConfig } from "./config.js";
|
import { loadConfig } from "./config.js";
|
||||||
import log4js from "log4js";
|
import log4js from "log4js";
|
||||||
import { createElectronWindow } from "./electron/window.js";
|
import { createElectronWindow } from "./electron/window.js";
|
||||||
import { setupIPC } from "./electron/ipc.js";
|
import { setupIPC } from "./electron/ipc.js";
|
||||||
|
import { LOCAL_SERVER_PORT, LocalServer } from "./localserver/localserver.js";
|
||||||
import { SocketClient } from "./socket/socketclient.js";
|
import { SocketClient } from "./socket/socketclient.js";
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
@ -12,7 +14,89 @@ logger.level = config.logLevel;
|
|||||||
|
|
||||||
const isHeadless = process.argv.includes("--headless");
|
const isHeadless = process.argv.includes("--headless");
|
||||||
|
|
||||||
|
function getArgValue(flag) {
|
||||||
|
const withEquals = process.argv.find((arg) => arg.startsWith(`${flag}=`));
|
||||||
|
if (withEquals) {
|
||||||
|
return withEquals.slice(flag.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = process.argv.indexOf(flag);
|
||||||
|
if (index !== -1 && index + 1 < process.argv.length) {
|
||||||
|
return process.argv[index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const otpCode = getArgValue("--otp") || undefined;
|
||||||
|
|
||||||
|
async function checkRunning() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`http://127.0.0.1:${LOCAL_SERVER_PORT}/info`,
|
||||||
|
{ timeout: 1000 },
|
||||||
|
);
|
||||||
|
if (response.data?.running === true) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authenticateWithOtp(otpCode) {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`http://127.0.0.1:${LOCAL_SERVER_PORT}/otpAuth`,
|
||||||
|
{ otp: otpCode },
|
||||||
|
{ timeout: 30000 },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response?.data) {
|
||||||
|
return err.response.data;
|
||||||
|
}
|
||||||
|
throw new Error("Failed to authenticate with OTP");
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function init() {
|
export async function init() {
|
||||||
|
logger.info("⌛ Checking if Farm Control Server is running...");
|
||||||
|
|
||||||
|
const runningServer = await checkRunning();
|
||||||
|
|
||||||
|
if (otpCode != undefined) {
|
||||||
|
logger.info("✔ Farm Control Server is running.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otpCode != undefined) {
|
||||||
|
logger.info("🔒 Authenticating with OTP...");
|
||||||
|
const authenticatedServer = await authenticateWithOtp(otpCode);
|
||||||
|
if (authenticatedServer.valid == false) {
|
||||||
|
logger.error(
|
||||||
|
"✘ Failed to authenticate with OTP -",
|
||||||
|
authenticatedServer.error,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.info("✔ Authenticated with OTP.");
|
||||||
|
logger.info("----------Host----------");
|
||||||
|
logger.info("Name:", authenticatedServer.host?.name || "Unknown");
|
||||||
|
logger.info("Tags: -", authenticatedServer.host?.tags?.[0] || "n/a");
|
||||||
|
for (var i = 0; i < authenticatedServer.host?.tags?.length - 1; i++) {
|
||||||
|
const tag = authenticatedServer.host?.tags[i + 1];
|
||||||
|
logger.info(" -", tag);
|
||||||
|
}
|
||||||
|
logger.info("State:", "Online");
|
||||||
|
logger.info("------------------------");
|
||||||
|
return authenticatedServer;
|
||||||
|
}
|
||||||
|
return authenticatedServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningServer != false) {
|
||||||
|
logger.warn("✘ Farm Control Server is already running.");
|
||||||
|
return runningServer;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isHeadless) {
|
if (!isHeadless) {
|
||||||
// Create Electron window first
|
// Create Electron window first
|
||||||
logger.info("Creating electron window...");
|
logger.info("Creating electron window...");
|
||||||
@ -31,13 +115,21 @@ export async function init() {
|
|||||||
const socketClient = new SocketClient();
|
const socketClient = new SocketClient();
|
||||||
// Make socket client globally accessible for IPC handlers
|
// Make socket client globally accessible for IPC handlers
|
||||||
global.socketClient = socketClient;
|
global.socketClient = socketClient;
|
||||||
|
|
||||||
|
const localServer = new LocalServer(socketClient);
|
||||||
|
await localServer.start();
|
||||||
|
|
||||||
socketClient.connect();
|
socketClient.connect();
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
logger.info("Shutting down...");
|
logger.info("Shutting down...");
|
||||||
socketClient.disconnect();
|
socketClient.disconnect();
|
||||||
|
localServer.stop();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init().catch((err) => {
|
||||||
|
logger.error(err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
76
src/localserver/localserver.js
Normal file
76
src/localserver/localserver.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import express from "express";
|
||||||
|
import log4js from "log4js";
|
||||||
|
import { notPrompting } from "../utils.js";
|
||||||
|
import { loadConfig } from "../config.js";
|
||||||
|
|
||||||
|
export const LOCAL_SERVER_PORT = 47291;
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
const logger = log4js.getLogger("Local Server");
|
||||||
|
logger.level = config.logLevel;
|
||||||
|
|
||||||
|
export class LocalServer {
|
||||||
|
constructor(socketClient) {
|
||||||
|
this.socketClient = socketClient;
|
||||||
|
this.server = null;
|
||||||
|
this.app = express();
|
||||||
|
this.app.use(express.json());
|
||||||
|
this.setupRoutes();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRoutes() {
|
||||||
|
this.app.get("/info", (_req, res) => {
|
||||||
|
logger.debug("Received request to check if server is running");
|
||||||
|
res.json({ running: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.post("/otpAuth", async (req, res) => {
|
||||||
|
logger.debug("Received request to authenticate with OTP");
|
||||||
|
const { otp } = req.body;
|
||||||
|
|
||||||
|
if (!otp) {
|
||||||
|
res.status(400).json({ error: "otp is required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info("Authenticating with OTP via local server...");
|
||||||
|
notPrompting();
|
||||||
|
const result = await this.socketClient.authenticateWithOtp(otp, {
|
||||||
|
retryOnFailure: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.valid) {
|
||||||
|
res.status(401).json(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("OTP authentication failed:", err);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.server = this.app.listen(LOCAL_SERVER_PORT, "127.0.0.1", () => {
|
||||||
|
logger.info(
|
||||||
|
`Local server listening on http://127.0.0.1:${LOCAL_SERVER_PORT}`,
|
||||||
|
);
|
||||||
|
resolve(this.server);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.on("error", reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.server) {
|
||||||
|
this.server.close();
|
||||||
|
this.server = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -119,49 +119,109 @@ export class SocketClient {
|
|||||||
this.socket.disconnect();
|
this.socket.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateWithOtp(otp = undefined) {
|
async waitForConnection(timeoutMs = 10000) {
|
||||||
|
if (this.socket?.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.socket) {
|
||||||
|
throw new Error("Socket client is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
this.socket.off("connect", onConnect);
|
||||||
|
reject(new Error("Socket connection timed out"));
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
const onConnect = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.socket.connected) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.once("connect", onConnect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticateWithOtp(otp = undefined, options = {}) {
|
||||||
|
const { retryOnFailure = otp == undefined } = options;
|
||||||
|
|
||||||
if (otp == undefined) {
|
if (otp == undefined) {
|
||||||
otp = await askOtp();
|
otp = await askOtp();
|
||||||
}
|
}
|
||||||
await this.authenticate({ otp: otp });
|
|
||||||
return;
|
await this.waitForConnection();
|
||||||
|
return this.authenticate({ otp }, { retryOnFailure });
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(authenticationData) {
|
async authenticate(authenticationData, options = {}) {
|
||||||
logger.debug("Host authenticating...");
|
const { retryOnFailure = true } = options;
|
||||||
this.socket.emit(
|
|
||||||
"authenticate",
|
|
||||||
authenticationData,
|
|
||||||
async (verifyResult) => {
|
|
||||||
if (verifyResult.valid == false) {
|
|
||||||
this.authenticated = false;
|
|
||||||
sendIPC("setAuthenticated", false);
|
|
||||||
logger.error("Host not authenticated:", verifyResult.error);
|
|
||||||
await this.authenticateWithOtp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info("Host authenticated.");
|
|
||||||
this.authenticated = true;
|
|
||||||
sendIPC("setAuthenticated", true);
|
|
||||||
sendIPC("setLoading", false);
|
|
||||||
this.loading = false;
|
|
||||||
this.host = verifyResult.host;
|
|
||||||
sendIPC("setHost", {
|
|
||||||
...this.host,
|
|
||||||
online: true,
|
|
||||||
state: { type: "online" },
|
|
||||||
});
|
|
||||||
this.id = this.host._id;
|
|
||||||
|
|
||||||
config.host = { id: this.id, authCode: this.host.authCode };
|
await this.waitForConnection();
|
||||||
saveConfig(config);
|
|
||||||
this.sendDeviceInfo();
|
return new Promise((resolve, reject) => {
|
||||||
this.subscribeToObjectEvents();
|
if (!this.socket?.connected) {
|
||||||
this.documentPrinterManager.reloadDocumentPrinters();
|
reject(new Error("Socket client is not connected"));
|
||||||
this.printerManager.reloadPrinters();
|
return;
|
||||||
await this.fileManager.updateFiles();
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
logger.debug("Host authenticating...");
|
||||||
|
this.socket.emit(
|
||||||
|
"authenticate",
|
||||||
|
authenticationData,
|
||||||
|
async (verifyResult) => {
|
||||||
|
if (verifyResult.valid == false) {
|
||||||
|
this.authenticated = false;
|
||||||
|
sendIPC("setAuthenticated", false);
|
||||||
|
logger.error("Host not authenticated:", verifyResult.error);
|
||||||
|
|
||||||
|
if (retryOnFailure) {
|
||||||
|
const retryResult = await this.authenticateWithOtp();
|
||||||
|
resolve(retryResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({ valid: false, error: verifyResult.error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Host authenticated.");
|
||||||
|
this.authenticated = true;
|
||||||
|
sendIPC("setAuthenticated", true);
|
||||||
|
sendIPC("setLoading", false);
|
||||||
|
this.loading = false;
|
||||||
|
if (this.host) {
|
||||||
|
sendIPC("setHost", {
|
||||||
|
...this.host,
|
||||||
|
online: false,
|
||||||
|
state: { type: "offline" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.host = verifyResult.host;
|
||||||
|
sendIPC("setHost", {
|
||||||
|
...this.host,
|
||||||
|
online: true,
|
||||||
|
state: { type: "online" },
|
||||||
|
});
|
||||||
|
this.id = this.host._id;
|
||||||
|
|
||||||
|
config.host = { id: this.id, authCode: this.host.authCode };
|
||||||
|
saveConfig(config);
|
||||||
|
this.sendDeviceInfo();
|
||||||
|
this.subscribeToObjectEvents();
|
||||||
|
this.documentPrinterManager.reloadDocumentPrinters();
|
||||||
|
this.printerManager.reloadPrinters();
|
||||||
|
await this.fileManager.updateFiles();
|
||||||
|
resolve({ valid: true, host: this.host });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHostAction(action, callback) {
|
handleHostAction(action, callback) {
|
||||||
@ -194,7 +254,7 @@ export class SocketClient {
|
|||||||
this.documentPrinterManager.handleDocumentPrinterAction(
|
this.documentPrinterManager.handleDocumentPrinterAction(
|
||||||
id,
|
id,
|
||||||
action,
|
action,
|
||||||
callback
|
callback,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +264,7 @@ export class SocketClient {
|
|||||||
"Got object update for type:",
|
"Got object update for type:",
|
||||||
data.objectType,
|
data.objectType,
|
||||||
" id:",
|
" id:",
|
||||||
data._id
|
data._id,
|
||||||
);
|
);
|
||||||
if (data.objectType == "host") {
|
if (data.objectType == "host") {
|
||||||
this.handleHostAction(action, callback);
|
this.handleHostAction(action, callback);
|
||||||
@ -213,7 +273,7 @@ export class SocketClient {
|
|||||||
this.documentPrinterManager.handleDocumentPrinterUpdate(
|
this.documentPrinterManager.handleDocumentPrinterUpdate(
|
||||||
data._id,
|
data._id,
|
||||||
data,
|
data,
|
||||||
callback
|
callback,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.objectType == "printer") {
|
if (data.objectType == "printer") {
|
||||||
@ -315,7 +375,7 @@ export class SocketClient {
|
|||||||
});
|
});
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -342,7 +402,7 @@ export class SocketClient {
|
|||||||
});
|
});
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -374,7 +434,7 @@ export class SocketClient {
|
|||||||
});
|
});
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -404,7 +464,7 @@ export class SocketClient {
|
|||||||
});
|
});
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -537,7 +597,7 @@ export class SocketClient {
|
|||||||
// Listen for found services
|
// Listen for found services
|
||||||
this.scanner.on("serviceFound", (data) => {
|
this.scanner.on("serviceFound", (data) => {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Found websocket service at ${data.hostname} (${data.ip})`
|
`Found websocket service at ${data.hostname} (${data.ip})`,
|
||||||
);
|
);
|
||||||
this.socket.emit("notify_scan_network_found", data);
|
this.socket.emit("notify_scan_network_found", data);
|
||||||
});
|
});
|
||||||
@ -545,7 +605,7 @@ export class SocketClient {
|
|||||||
// Listen for scan progress
|
// Listen for scan progress
|
||||||
this.scanner.on("scanProgress", ({ currentIP, progress }) => {
|
this.scanner.on("scanProgress", ({ currentIP, progress }) => {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Scanning ${currentIP} (${progress.toFixed(2)}% complete)`
|
`Scanning ${currentIP} (${progress.toFixed(2)}% complete)`,
|
||||||
);
|
);
|
||||||
this.socket.emit("notify_scan_network_progress", {
|
this.socket.emit("notify_scan_network_progress", {
|
||||||
currentIP: currentIP,
|
currentIP: currentIP,
|
||||||
@ -558,7 +618,7 @@ export class SocketClient {
|
|||||||
"Scanning network for websocket services on port:",
|
"Scanning network for websocket services on port:",
|
||||||
data?.port || 7125,
|
data?.port || 7125,
|
||||||
"using protocol:",
|
"using protocol:",
|
||||||
data?.protocol || "ws"
|
data?.protocol || "ws",
|
||||||
);
|
);
|
||||||
this.scanner
|
this.scanner
|
||||||
.scanNetwork(data?.port || 7125, data?.protocol || "ws")
|
.scanNetwork(data?.port || 7125, data?.protocol || "ws")
|
||||||
@ -641,7 +701,7 @@ export class SocketClient {
|
|||||||
|
|
||||||
// Get the printer client
|
// Get the printer client
|
||||||
const printerClient = this.printerManager.getPrinterClient(
|
const printerClient = this.printerManager.getPrinterClient(
|
||||||
data.printerId
|
data.printerId,
|
||||||
);
|
);
|
||||||
if (!printerClient) {
|
if (!printerClient) {
|
||||||
throw new Error(`Printer with ID ${data.printerId} not found`);
|
throw new Error(`Printer with ID ${data.printerId} not found`);
|
||||||
@ -649,7 +709,7 @@ export class SocketClient {
|
|||||||
|
|
||||||
// Load the filament stock
|
// Load the filament stock
|
||||||
const result = await printerClient.loadFilamentStock(
|
const result = await printerClient.loadFilamentStock(
|
||||||
data.filamentStockId
|
data.filamentStockId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user