mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
Add authentication plugin that does a simple challenge response protocol to verify that the player trying to log in is who they claim to be. The process goes as follows: 1. A player code is generated for a player that wants to log in and is displayed in-game. 2. The player code is input into the web interface and the master generates a verification code as a challenge back along with a token. 3. The player inputs the verification code in-game and is logged in to the web interface in doing so. To do the actual login the web interface polls a verify endpoint every 2 seconds until the code has either been verified or expired. The challenge comes from being unable to control the verification code and having to input it as a player logging into one of the servers in order to login. Some additional steps are taken in order to increase the difficulty for an attacker to subvert the challenge: - The login process must be started from in-game. - Generated codes are only valid for a limited timespan which is configurable and 2 minutes by default. - The verification code comes with a token that only works with one player code and verification code combo and must be provided to pass validation.
56 lines
1.5 KiB
JavaScript
56 lines
1.5 KiB
JavaScript
/**
|
|
* @module
|
|
*/
|
|
"use strict";
|
|
const libPlugin = require("@clusterio/lib/plugin");
|
|
const libLuaTools = require("@clusterio/lib/lua_tools");
|
|
|
|
|
|
class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
|
async init() {
|
|
if (!this.instance.config.get("factorio.enable_save_patching")) {
|
|
throw new Error("player_auth plugin requires save patching.");
|
|
}
|
|
|
|
this.instance.server.on("ipc-player_auth", event => this.handleEvent(event).catch(
|
|
err => this.logger.error(`Error handling event:\n${err.stack}`)
|
|
));
|
|
}
|
|
|
|
async handleEvent(event) {
|
|
if (event.type === "open_dialog") {
|
|
if (!this.slave.connector.connected) {
|
|
await this.sendRcon(`/web-login error ${event.player} login is temporarily unavailable`);
|
|
return;
|
|
}
|
|
|
|
let response;
|
|
try {
|
|
response = await this.info.messages.fetchPlayerCode.send(this.instance, { player: event.player });
|
|
} catch (err) {
|
|
await this.sendRcon(`/web-login error ${event.player} ${err.message}`);
|
|
return;
|
|
}
|
|
await this.sendRcon(`/web-login open ${event.player} ${response.master_url} ${response.player_code}`);
|
|
|
|
} else if (event.type === "set_verify_code") {
|
|
try {
|
|
await this.info.messages.setVerifyCode.send(this.instance, {
|
|
player: event.player,
|
|
verify_code: event.verify_code,
|
|
});
|
|
|
|
} catch (err) {
|
|
await this.sendRcon(`/web-login error ${event.player} ${err.message}`);
|
|
return;
|
|
}
|
|
|
|
await this.sendRcon(`/web-login code_set ${event.player}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
InstancePlugin,
|
|
};
|