From b2705b87881b9d87a45c8224e666207abfb02c2d Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 8 Oct 2026 05:15:00 +0000 Subject: [PATCH] Convert player_auth to entrypoint functions Co-Authored-By: Claude Opus 5.5 --- plugins/player_auth/controller.ts | 165 ++++++++++----------- plugins/player_auth/instance.ts | 40 ++--- plugins/player_auth/test/plugin.js | 227 +++++++++++++++-------------- plugins/player_auth/web/index.tsx | 19 ++- 4 files changed, 225 insertions(+), 226 deletions(-) diff --git a/plugins/player_auth/controller.ts b/plugins/player_auth/controller.ts index 19dcce81..38b6d947 100644 --- a/plugins/player_auth/controller.ts +++ b/plugins/player_auth/controller.ts @@ -4,7 +4,7 @@ import util from "util"; import jwt from "jsonwebtoken"; import { Static } from "@sinclair/typebox"; -import { BaseControllerPlugin } from "@clusterio/controller"; +import type { ControllerPluginContext } from "@clusterio/controller"; import { basicType, RequestError } from "@clusterio/lib"; import { FetchPlayerCodeRequest, PlayerAuthServer, SetVerifyCodeRequest } from "./messages.js"; @@ -28,75 +28,88 @@ async function generateCode(length: number): Promise { type PlayerCode = { playerCode: string, verifyCode: string | null, expiresMs: number }; -export class ControllerPlugin extends BaseControllerPlugin { - players!: Map; +export default async function loadControllerPlugin(context: ControllerPluginContext) { + const { controller } = context; - async init() { - // Store of validation attempts by players - this.players = new Map(); + // Store of validation attempts by players + const players = new Map(); - // Periodically remove expired entries - setInterval(() => { - let now = Date.now(); - for (let [player, entry] of this.players) { - if (entry.expiresMs < now) { - this.players.delete(player); - } + // Periodically remove expired entries + setInterval(() => { + let now = Date.now(); + for (let [player, entry] of players) { + if (entry.expiresMs < now) { + players.delete(player); } - }, 60e3).unref(); + } + }, 60e3).unref(); - this.controller.app.get("/api/player_auth/servers", (req: Request, res: Response) => { - const servers: Static[] = []; + controller.app.get("/api/player_auth/servers", (req: Request, res: Response) => { + const servers: Static[] = []; - for (const instance of this.controller.instances.values()) { - const pluginLoaded = instance.config.get("player_auth.load_plugin"); - const assignedHost = instance.config.get("instance.assigned_host"); - if (instance.status !== "running" || !pluginLoaded || assignedHost === null) { - continue; - } - - const host = this.controller.hosts.get(assignedHost); - if (!host) { - continue; - } - - const address = instance.gamePort !== undefined - ? `${host.publicAddress}:${instance.gamePort}` - : host.publicAddress; - - const settings = instance.config.get("factorio.settings"); - const includeAddress = this.controller.config.get("player_auth.show_connect_address"); - servers.push({ - name: settings["name"] as string || "unnamed server", - factorioVersion: instance.factorioVersion, - address: (includeAddress && host.publicAddress !== "") ? address : undefined, - }); + for (const instance of controller.instances.values()) { + const pluginLoaded = instance.config.get("player_auth.load_plugin"); + const assignedHost = instance.config.get("instance.assigned_host"); + if (instance.status !== "running" || !pluginLoaded || assignedHost === null) { + continue; } - res.send(servers); - }); - - this.controller.app.post( - "/api/player_auth/player_code", - express.json(), - (req: Request, res: Response, next: any) => { - this.handlePlayerCode(req, res).catch(next); + const host = controller.hosts.get(assignedHost); + if (!host) { + continue; } - ); - this.controller.app.post( - "/api/player_auth/verify", - express.json(), - (req: Request, res: Response, next: any) => { - this.handleVerify(req, res).catch(next); - } - ); + const address = instance.gamePort !== undefined + ? `${host.publicAddress}:${instance.gamePort}` + : host.publicAddress; - this.controller.handle(FetchPlayerCodeRequest, this.handleFetchPlayerCodeRequest.bind(this)); - this.controller.handle(SetVerifyCodeRequest, this.handleSetVerifyCodeRequest.bind(this)); - } + const settings = instance.config.get("factorio.settings"); + const includeAddress = controller.config.get("player_auth.show_connect_address"); + servers.push({ + name: settings["name"] as string || "unnamed server", + factorioVersion: instance.factorioVersion, + address: (includeAddress && host.publicAddress !== "") ? address : undefined, + }); + } - async handlePlayerCode(req: Request, res: Response) { + res.send(servers); + }); + + controller.app.post( + "/api/player_auth/player_code", + express.json(), + (req: Request, res: Response, next: any) => { + handlePlayerCode(req, res).catch(next); + } + ); + + controller.app.post( + "/api/player_auth/verify", + express.json(), + (req: Request, res: Response, next: any) => { + handleVerify(req, res).catch(next); + } + ); + + controller.handle(FetchPlayerCodeRequest, async (request: FetchPlayerCodeRequest) => { + let playerCode = await generateCode(controller.config.get("player_auth.code_length")); + let expiresMs = Date.now() + controller.config.get("player_auth.code_timeout") * 1000; + players.set(request.player, { playerCode, verifyCode: null, expiresMs }); + return { playerCode, controllerUrl: controller.getControllerUrl() }; + }); + + controller.handle(SetVerifyCodeRequest, async (request: SetVerifyCodeRequest) => { + let { player, verifyCode } = request; + + let entry = players.get(player); + if (!entry || entry.expiresMs < Date.now()) { + throw new RequestError("invalid player"); + } + + entry.verifyCode = verifyCode; + }); + + async function handlePlayerCode(req: Request, res: Response) { if (basicType(req.body) !== "object") { res.sendStatus(400); return; @@ -108,9 +121,9 @@ export class ControllerPlugin extends BaseControllerPlugin { return; } - for (let entry of this.players.values()) { + for (let entry of players.values()) { if (entry.playerCode === playerCode && entry.expiresMs > Date.now()) { - let verifyCode = await generateCode(this.controller.config.get("player_auth.code_length")); + let verifyCode = await generateCode(controller.config.get("player_auth.code_length")); let verifyToken = jwt.sign( { aud: "player_auth.verify_code", @@ -118,7 +131,7 @@ export class ControllerPlugin extends BaseControllerPlugin { verify_code: verifyCode, player_code: playerCode, }, - this.controller.authSecret + controller.authSecret ); res.send({ verify_code: verifyCode, verify_token: verifyToken }); @@ -129,7 +142,7 @@ export class ControllerPlugin extends BaseControllerPlugin { res.send({ error: true, message: "invalid player_code" }); } - async handleVerify(req: Request, res: Response) { + async function handleVerify(req: Request, res: Response) { if (basicType(req.body) !== "object") { res.sendStatus(400); return; @@ -155,7 +168,7 @@ export class ControllerPlugin extends BaseControllerPlugin { try { let payload = jwt.verify( - verifyToken, this.controller.authSecret, { audience: "player_auth.verify_code" } + verifyToken, controller.authSecret, { audience: "player_auth.verify_code" } ) as jwt.JwtPayload; if (payload.verify_code !== verifyCode) { @@ -171,16 +184,16 @@ export class ControllerPlugin extends BaseControllerPlugin { return; } - for (let [player, entry] of this.players) { + for (let [player, entry] of players) { if (entry.playerCode === playerCode && entry.expiresMs > Date.now()) { if (entry.verifyCode === verifyCode) { - let user = this.controller.users.getByName(player); + let user = controller.users.getByName(player); if (!user) { res.send({ error: true, message: "invalid user" }); return; } - let token = this.controller.users.signUserToken(user); + let token = controller.users.signUserToken(user); res.send({ verified: true, token }); return; @@ -193,24 +206,6 @@ export class ControllerPlugin extends BaseControllerPlugin { res.send({ error: true, message: "invalid player_code" }); } - - async handleFetchPlayerCodeRequest(request: FetchPlayerCodeRequest) { - let playerCode = await generateCode(this.controller.config.get("player_auth.code_length")); - let expiresMs = Date.now() + this.controller.config.get("player_auth.code_timeout") * 1000; - this.players.set(request.player, { playerCode, verifyCode: null, expiresMs }); - return { playerCode, controllerUrl: this.controller.getControllerUrl() }; - } - - async handleSetVerifyCodeRequest(request: SetVerifyCodeRequest) { - let { player, verifyCode } = request; - - let entry = this.players.get(player); - if (!entry || entry.expiresMs < Date.now()) { - throw new RequestError("invalid player"); - } - - entry.verifyCode = verifyCode; - } } // For testing only diff --git a/plugins/player_auth/instance.ts b/plugins/player_auth/instance.ts index 58be8518..d38b4b45 100644 --- a/plugins/player_auth/instance.ts +++ b/plugins/player_auth/instance.ts @@ -1,4 +1,4 @@ -import { BaseInstancePlugin } from "@clusterio/host"; +import type { InstancePluginContext } from "@clusterio/host"; import { FetchPlayerCodeRequest, SetVerifyCodeRequest } from "./messages.js"; @@ -11,42 +11,46 @@ type IpcPlayerAuth = { verify_code: string, } -export class InstancePlugin extends BaseInstancePlugin { - async init() { - this.instance.server.on( - "ipc-player_auth", - (ipcPlayerAuth: IpcPlayerAuth) => this.handleEvent(ipcPlayerAuth).catch( - err => this.logger.error(`Error handling event:\n${err.stack}`) - ) - ); +export default async function loadInstancePlugin(context: InstancePluginContext) { + const { instance, host, logger, plugin } = context; + + async function sendRcon(command: string) { + await instance.sendRcon(command, false, plugin.name); } - async handleEvent(event: IpcPlayerAuth) { + async function handleEvent(event: IpcPlayerAuth) { if (event.type === "open_dialog") { - if (!this.host.connector.connected) { - await this.sendRcon(`/web-login error ${event.player} login is temporarily unavailable`); + if (!host.connector.connected) { + await sendRcon(`/web-login error ${event.player} login is temporarily unavailable`); return; } let response; try { - response = await this.instance.sendTo("controller", new FetchPlayerCodeRequest(event.player)); + response = await instance.sendTo("controller", new FetchPlayerCodeRequest(event.player)); } catch (err: any) { - await this.sendRcon(`/web-login error ${event.player} ${err.message}`); + await sendRcon(`/web-login error ${event.player} ${err.message}`); return; } - await this.sendRcon(`/web-login open ${event.player} ${response.controllerUrl} ${response.playerCode}`); + await sendRcon(`/web-login open ${event.player} ${response.controllerUrl} ${response.playerCode}`); } else if (event.type === "set_verify_code") { try { - await this.instance.sendTo("controller", new SetVerifyCodeRequest(event.player, event.verify_code)); + await instance.sendTo("controller", new SetVerifyCodeRequest(event.player, event.verify_code)); } catch (err: any) { - await this.sendRcon(`/web-login error ${event.player} ${err.message}`); + await sendRcon(`/web-login error ${event.player} ${err.message}`); return; } - await this.sendRcon(`/web-login code_set ${event.player}`); + await sendRcon(`/web-login code_set ${event.player}`); } } + + instance.server.on( + "ipc-player_auth", + (ipcPlayerAuth: IpcPlayerAuth) => handleEvent(ipcPlayerAuth).catch( + err => logger.error(`Error handling event:\n${err.stack}`) + ) + ); } diff --git a/plugins/player_auth/test/plugin.js b/plugins/player_auth/test/plugin.js index 590af809..1eef21fc 100644 --- a/plugins/player_auth/test/plugin.js +++ b/plugins/player_auth/test/plugin.js @@ -4,7 +4,7 @@ import jwt from "jsonwebtoken"; import * as mock from "../../../test/mock.js"; import * as controller from "../dist/node/controller.js"; -import * as instance from "../dist/node/instance.js"; +import * as instancePlugin from "../dist/node/instance.js"; import { plugin as info } from "../dist/node/index.js"; import { FetchPlayerCodeRequest, SetVerifyCodeRequest, PlayerAuthServer } from "../dist/node/messages.js"; import { testRoundTripJsonSerialisable, testMatrix } from "../../../test/common.js"; @@ -81,27 +81,54 @@ describe("player_auth", function() { }); }); - describe("ControllerPlugin", function() { - let controllerPlugin; + describe("entrypoint", function() { + let mockController; let controllerUrl; let endpoint; + let fetchPlayerCode; + let setVerifyCode; + + // Adds a pending code for player through the request handlers + async function addPlayer(player, { timeout = 1, verifyCode } = {}) { + mockController.mockConfigEntries.set("player_auth.code_timeout", timeout); + try { + const { playerCode } = await fetchPlayerCode(new FetchPlayerCodeRequest(player)); + if (verifyCode) { + await setVerifyCode(new SetVerifyCodeRequest(player, verifyCode)); + } + return playerCode; + } finally { + mockController.mockConfigEntries.set("player_auth.code_timeout", 1); + } + } + + function signVerifyToken(playerCode, verifyCode) { + return jwt.sign({ + aud: "player_auth.verify_code", + player_code: playerCode, + verify_code: verifyCode, + }, Buffer.from("TestSecretDoNotUse", "base64")); + } + before(async function() { - controllerPlugin = await mock.createControllerPlugin(controller.ControllerPlugin, info); - controllerPlugin.controller.mockConfigEntries.set("player_auth.code_length", 10); - controllerPlugin.controller.mockConfigEntries.set("player_auth.code_timeout", 1); - controllerPlugin.controller.mockConfigEntries.set("player_auth.show_connect_address", true); - controllerUrl = await controllerPlugin.controller.startServer(); + mockController = await mock.loadControllerPlugin(controller.default, info); + mockController.mockConfigEntries.set("player_auth.code_length", 10); + mockController.mockConfigEntries.set("player_auth.code_timeout", 1); + mockController.mockConfigEntries.set("player_auth.show_connect_address", true); + fetchPlayerCode = mock.getHandler(mockController, FetchPlayerCodeRequest); + setVerifyCode = mock.getHandler(mockController, SetVerifyCodeRequest); + controllerUrl = await mockController.startServer(); }); after(async function() { - if (controllerPlugin) { - await controllerPlugin.controller.stopServer(); + if (mockController) { + await mockController.stopServer(); } }); describe("/api/player_auth/servers", function() { it("should return a list of running servers with player_auth loaded", async function() { function addInstance(id, status, load, name, version, gamePort, host=1) { - controllerPlugin.controller.instances.records.set(id, { + mockController.instances.records.set(id, { config: { get(field) { if (field === "player_auth.load_plugin") { @@ -135,7 +162,7 @@ describe("player_auth", function() { addInstance(8, "running", true, "invalid host", "1.1.8", 34197, 2); addInstance(9, "running", true, "no version", undefined, 34197); - controllerPlugin.controller.hosts.set(1, { + mockController.hosts.set(1, { publicAddress: "127.0.0.1", }); @@ -170,15 +197,13 @@ describe("player_auth", function() { } }); it("should return invalid player_code if the code is expired", async function() { - let expiresMs = Date.now() - 1000; - controllerPlugin.players.set("expired", { playerCode: "expired", verifyCode: null, expiresMs }); - const result = await postJSON(endpoint, { player_code: "expired" }); + const playerCode = await addPlayer("expired", { timeout: -1 }); + const result = await postJSON(endpoint, { player_code: playerCode }); assert.deepEqual(await result.json(), { error: true, message: "invalid player_code" }); }); it("should return a verify code and token if code is valid", async function() { - let expiresMs = Date.now() + 1000; - controllerPlugin.players.set("valid", { playerCode: "valid", verifyCode: null, expiresMs }); - const result = await postJSON(endpoint, { player_code: "valid" }); + const playerCode = await addPlayer("valid"); + const result = await postJSON(endpoint, { player_code: playerCode }); const body = await result.json(); assert.equal(typeof body.verify_code, "string"); assert.equal(typeof body.verify_token, "string"); @@ -232,61 +257,38 @@ describe("player_auth", function() { await verify({ aud: "not_player_auth" }); }); it("should return invalid player_code if the code is expired", async function() { - let expiresMs = Date.now() - 1000; - controllerPlugin.players.set("expired", { playerCode: "expired", verifyCode: "verify", expiresMs }); + const playerCode = await addPlayer("expired", { timeout: -1 }); const result = await postJSON(endpoint, { - player_code: "expired", + player_code: playerCode, verify_code: "verify", - verify_token: jwt.sign({ - aud: "player_auth.verify_code", - player_code: "expired", - verify_code: "verify", - }, Buffer.from("TestSecretDoNotUse", "base64")), + verify_token: signVerifyToken(playerCode, "verify"), }); assert.deepEqual(await result.json(), { error: true, message: "invalid player_code" }); }); it("should return verified false if verify code has not yet been set", async function() { - let expiresMs = Date.now() + 1000; - controllerPlugin.players.set( - "unverified", - { playerCode: "unverified", verifyCode: null, expiresMs }, - ); + const playerCode = await addPlayer("unverified"); const result = await postJSON(endpoint, { - player_code: "unverified", + player_code: playerCode, verify_code: "verify", - verify_token: jwt.sign({ - aud: "player_auth.verify_code", - player_code: "unverified", - verify_code: "verify", - }, Buffer.from("TestSecretDoNotUse", "base64")), + verify_token: signVerifyToken(playerCode, "verify"), }); assert.deepEqual(await result.json(), { verified: false }); }); it("should return error if user is missing", async function() { - let expiresMs = Date.now() + 1000; - controllerPlugin.players.set("missing", { playerCode: "missing", verifyCode: "verify", expiresMs }); + const playerCode = await addPlayer("missing", { verifyCode: "verify" }); const result = await postJSON(endpoint, { - player_code: "missing", + player_code: playerCode, verify_code: "verify", - verify_token: jwt.sign({ - aud: "player_auth.verify_code", - player_code: "missing", - verify_code: "verify", - }, Buffer.from("TestSecretDoNotUse", "base64")), + verify_token: signVerifyToken(playerCode, "verify"), }); assert.deepEqual(await result.json(), { error: true, message: "invalid user" }); }); it("should return verified true with token if valid verification", async function() { - let expiresMs = Date.now() + 1000; - controllerPlugin.players.set("player", { playerCode: "player", verifyCode: "verify", expiresMs }); + const playerCode = await addPlayer("player", { verifyCode: "verify" }); const result = await postJSON(endpoint, { - player_code: "player", + player_code: playerCode, verify_code: "verify", - verify_token: jwt.sign({ - aud: "player_auth.verify_code", - player_code: "player", - verify_code: "verify", - }, Buffer.from("TestSecretDoNotUse", "base64")), + verify_token: signVerifyToken(playerCode, "verify"), }); const body = await result.json(); assert.equal(body.verified, true); @@ -294,35 +296,37 @@ describe("player_auth", function() { }); }); - describe(".handleFetchPlayerCodeRequest()", function() { + describe("FetchPlayerCodeRequest handler", function() { it("should return a code", async function() { - let result = await controllerPlugin.handleFetchPlayerCodeRequest( - new FetchPlayerCodeRequest("test") - ); + let result = await fetchPlayerCode(new FetchPlayerCodeRequest("test")); assert(typeof result.playerCode === "string", "no code returned"); assert(result.playerCode.length === 10, "incorrect code length returned"); - let expiresMs = controllerPlugin.players.get("test").expiresMs; - let msFromExpected = Math.abs(expiresMs - Date.now() - 1000); - assert(msFromExpected < 100, `expiry time expected outside window (${msFromExpected}ms)`); + assert.equal(result.controllerUrl, "http://controller.example/"); + }); + it("should expire the code after the timeout", async function() { + const start = Date.now(); + const playerCode = await addPlayer("test", { timeout: 10 }); + const end = Date.now(); + const result = await postJSON( + `${controllerUrl}/api/player_auth/player_code`, { player_code: playerCode } + ); + const { exp } = jwt.decode((await result.json()).verify_token); + assert(exp >= Math.floor((start + 10e3) / 1000), "expiry before expected window"); + assert(exp <= Math.floor((end + 10e3) / 1000), "expiry after expected window"); }); }); - describe(".handleSetVerifyCodeRequest()", function() { + describe("SetVerifyCodeRequest handler", function() { it("should throw if player does not exist", async function() { await assert.rejects( - controllerPlugin.handleSetVerifyCodeRequest( - new SetVerifyCodeRequest("invalid", "invalid") - ), + setVerifyCode(new SetVerifyCodeRequest("invalid", "invalid")), new lib.RequestError("invalid player") ); }); it("should throw if player code has expired", async function() { - let expiresMs = Date.now() - 1000; - controllerPlugin.players.set("expired", { playerCode: "expired", verifyCode: null, expiresMs }); + await addPlayer("expired", { timeout: -1 }); await assert.rejects( - controllerPlugin.handleSetVerifyCodeRequest( - new SetVerifyCodeRequest("expired", "expired") - ), + setVerifyCode(new SetVerifyCodeRequest("expired", "expired")), new lib.RequestError("invalid player") ); }); @@ -330,10 +334,7 @@ describe("player_auth", function() { describe("integration", function() { it("should verify a full login flow", async function() { - let app = controllerPlugin.controller.app; - let { playerCode } = await controllerPlugin.handleFetchPlayerCodeRequest( - new FetchPlayerCodeRequest("test") - ); + let { playerCode } = await fetchPlayerCode(new FetchPlayerCodeRequest("test")); const playerCodeResult = await postJSON( `${controllerUrl}/api/player_auth/player_code`, @@ -341,12 +342,10 @@ describe("player_auth", function() { ); let { verify_code, verify_token } = await playerCodeResult.json(); - await controllerPlugin.handleSetVerifyCodeRequest( - new SetVerifyCodeRequest("test", verify_code) - ); + await setVerifyCode(new SetVerifyCodeRequest("test", verify_code)); - const user = controllerPlugin.controller.users.getByName("test"); - const token = controllerPlugin.controller.users.signUserToken(user); + const user = mockController.users.getByName("test"); + const token = mockController.users.signUserToken(user); const verifyResult = await postJSON( `${controllerUrl}/api/player_auth/verify`, { player_code: playerCode, verify_code, verify_token } @@ -359,76 +358,78 @@ describe("player_auth", function() { }); describe("instance.js", function() { - describe("class InstancePlugin", function() { - let instancePlugin; + describe("entrypoint", function() { + let instance; + let host; before(async function() { - instancePlugin = await mock.createInstancePlugin(instance.InstancePlugin, info); + try { + lib.registerPluginMessages([info]); + } catch (err) { + // Already registered by the full test suite + } + ({ instance, host } = await mock.loadInstancePlugin(instancePlugin.default, info)); }); - describe(".handleEvent()", async function() { - describe("open_dialog", async function() { + // Emits an ipc-player_auth event and returns the first RCON command it sent + async function emitIpc(event) { + instance.server.reset(); + instance.server.emit("ipc-player_auth", event); + while (!instance.server.rconCommands.length) { + await new Promise(resolve => setImmediate(resolve)); + } + return instance.server.rconCommands[0]; + } + + describe("ipc-player_auth", function() { + describe("open_dialog", function() { it("should call /web-login error if not connected to controller", async function() { - instancePlugin.instance.server.reset(); - instancePlugin.host.connector.connected = false; - await instancePlugin.handleEvent({ type: "open_dialog", player: "test" }); - let command = instancePlugin.instance.server.rconCommands[0]; - instancePlugin.host.connector.connected = true; + host.connector.connected = false; + let command = await emitIpc({ type: "open_dialog", player: "test" }); + host.connector.connected = true; assert.equal(command, "/web-login error test login is temporarily unavailable"); }); it("should call /web-login error after error from the controller", async function() { - instancePlugin.instance.server.reset(); - instancePlugin.instance.connector.once("send", message => { - instancePlugin.instance.connector.emit("message", + instance.connector.once("send", message => { + instance.connector.emit("message", new lib.MessageResponseError(1, message.dst, message.src, new lib.ResponseError("controller error") ) ); }); - await instancePlugin.handleEvent({ type: "open_dialog", player: "test" }); - let command = instancePlugin.instance.server.rconCommands[0]; + let command = await emitIpc({ type: "open_dialog", player: "test" }); assert.equal(command, "/web-login error test controller error"); }); it("should call /web-login open after a valid response from the controller", async function() { - instancePlugin.instance.server.reset(); - instancePlugin.instance.connector.once("send", message => { - instancePlugin.instance.connector.emit("message", + instance.connector.once("send", message => { + instance.connector.emit("message", new lib.MessageResponse(1, message.dst, message.src, new FetchPlayerCodeRequest.Response("code", "controller-url") ) ); }); - await instancePlugin.handleEvent({ type: "open_dialog", player: "test" }); - let command = instancePlugin.instance.server.rconCommands[0]; + let command = await emitIpc({ type: "open_dialog", player: "test" }); assert.equal(command, "/web-login open test controller-url code"); }); }); - describe("open_dialog", async function() { + describe("set_verify_code", function() { it("should call /web-login code_set after a valid response from the controller", async function() { - instancePlugin.instance.server.reset(); - instancePlugin.instance.connector.once("send", message => { - instancePlugin.instance.connector.emit("message", + instance.connector.once("send", message => { + instance.connector.emit("message", new lib.MessageResponse(1, message.dst, message.src) ); }); - await instancePlugin.handleEvent( - { type: "set_verify_code", player: "test", verify_code: "verify" } - ); - let command = instancePlugin.instance.server.rconCommands[0]; + let command = await emitIpc({ type: "set_verify_code", player: "test", verify_code: "verify" }); assert.equal(command, "/web-login code_set test"); }); it("should call /web-login error after error from the controller", async function() { - instancePlugin.instance.server.reset(); - instancePlugin.instance.connector.once("send", message => { - instancePlugin.instance.connector.emit("message", + instance.connector.once("send", message => { + instance.connector.emit("message", new lib.MessageResponseError(1, message.dst, message.src, new lib.ResponseError("controller error") ) ); }); - await instancePlugin.handleEvent( - { type: "set_verify_code", player: "test", verify_code: "verify" } - ); - let command = instancePlugin.instance.server.rconCommands[0]; + let command = await emitIpc({ type: "set_verify_code", player: "test", verify_code: "verify" }); assert.equal(command, "/web-login error test controller error"); }); }); diff --git a/plugins/player_auth/web/index.tsx b/plugins/player_auth/web/index.tsx index 17cb7531..81c09f67 100644 --- a/plugins/player_auth/web/index.tsx +++ b/plugins/player_auth/web/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import { BaseWebPlugin, notifyErrorHandler } from "@clusterio/web_ui"; +import { notifyErrorHandler, type WebPluginContext } from "@clusterio/web_ui"; import { Alert, Button, Form, Input, Modal, Space, Spin, Typography } from "antd"; import { PlayerAuthServer } from "../messages.js"; @@ -251,13 +251,12 @@ function LoginForm(props: LoginFormProps) { } -export class WebPlugin extends BaseWebPlugin { - async init() { - this.logger.info("Player Auth init"); - this.loginForms = [{ - name: "player_auth.factorio", - title: "Factorio", - Component: LoginForm, - }]; - } +export default async function loadWebPlugin(context: WebPluginContext) { + const { control, logger, plugin } = context; + logger.info("Player Auth init"); + control.hooks.loginForms.attach(plugin.name, () => [{ + name: "player_auth.factorio", + title: "Factorio", + Component: LoginForm, + }]); }