fix(mods): 加载器 mod 仅在正版验证的独立服务器上签发绑定码,README 标明只用 limbo token 及其风险

This commit is contained in:
Lemon-miaow committed 2026-09-25 23:06:35 +08:00
1 parent f1414b5cc8
commit 2d1592bf01
5 files changed
+63 -3

No files matched your search

+21 -3
View File
@@ -1,7 +1,8 @@
# Felis server-side plugins
These are the in-cluster and edge plugins for Felis. The Velocity proxy and the three loader
mods ship the in-game first leg of the §10 account-link flow: a player who is already online
mods (these on a standalone online-mode server only, see the warning under the module table)
ship the in-game first leg of the §10 account-link flow: a player who is already online
(so Mojang has verified their UUID) runs `/link`; the plugin asks felis-api to
mint a one-time code for that UUID and shows it in chat. The player then enters
the code on the web console → **Account** page (the second leg), which binds the
@@ -40,6 +41,21 @@ no `felis-fabric`/`felis-forge`/`felis-neoforge` jar anywhere. They build from t
the commands under [Building](#building) and are deployed by hand; the account-link flow they
carry works, but nothing installs them for you.
> **The loader mods are for a standalone server only.** Inside a Felis network the proxy's
> `/link` already serves every backend and shadows a backend's own, so user game servers need
> no mod. A mod answers `/link` only when its server runs `online-mode=true`: an offline-mode
> server is either behind a proxy (whose `/link` applies) or cracked, where the UUID is
> whatever the client claims.
>
> **The token a mod holds is a real credential.** It mints a link code for any UUID the mod
> asks about, so whoever can read that server's files — its operator, any plugin or mod on
> it, a copied backup — can bind a not-yet-linked player's Minecraft account to their own web
> account. Put a mod only on a server whose operator you would trust with that, and give it
> the `limbo` token: it opens the link-code, link-status and blacklist routes and nothing
> else. The `velocity` token also approves op-logins and wakes or claims servers for any
> player; it stays on the proxy host. `sudo felis rotate-token limbo` replaces a leaked
> token (the login gate restarts onto the new value; copy it to the mod by hand).
## Architecture
Each platform is an **independent** Gradle build with its own `settings.gradle`,
@@ -232,8 +248,10 @@ Velocity), then set `api-base-url` and `service-token` — or provide
precedence. The token is one of felis-api's per-caller internal tokens: the
Velocity proxy uses the `velocity` token (Secret `felis/felis-service-token`), the
login gate the `limbo` token (`felis-limbo-token`), and each serves only its own
routes (a token on another caller's route gets `403 wrong_caller`). Treat it as a
secret; `sudo felis rotate-token <caller>` replaces it.
routes (a token on another caller's route gets `403 wrong_caller`). A loader mod
takes the `limbo` token, on a standalone online-mode server only (see the warning
under the module table). Treat it as a secret; `sudo felis rotate-token <caller>`
replaces it.
On **Velocity**, also set `root-domain` (and optionally `lobby-server`) in the
same file to turn on §11 routing, and make sure `online-mode=true` in
@@ -5,6 +5,7 @@ import best.lolicon.felis.link.LinkCode;
import best.lolicon.felis.link.LinkConfig;
import best.lolicon.felis.link.LinkConfigLoader;
import best.lolicon.felis.link.LinkException;
import best.lolicon.felis.link.ModLinkPolicy;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -68,6 +69,11 @@ public final class FelisFabricMod implements DedicatedServerModInitializer {
source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player."));
return 0;
}
if (!source.getServer().usesAuthentication()) {
LOGGER.warn(ModLinkPolicy.OFFLINE_LOG);
source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY));
return 0;
}
requestAndReply(source.getServer(), player);
return 1;
}));
@@ -5,6 +5,7 @@ import best.lolicon.felis.link.LinkCode;
import best.lolicon.felis.link.LinkConfig;
import best.lolicon.felis.link.LinkConfigLoader;
import best.lolicon.felis.link.LinkException;
import best.lolicon.felis.link.ModLinkPolicy;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -73,6 +74,11 @@ public final class FelisForgeMod {
source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player."));
return 0;
}
if (!source.getServer().usesAuthentication()) {
LOGGER.warn(ModLinkPolicy.OFFLINE_LOG);
source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY));
return 0;
}
requestAndReply(source.getServer(), player);
return 1;
}));
@@ -5,6 +5,7 @@ import best.lolicon.felis.link.LinkCode;
import best.lolicon.felis.link.LinkConfig;
import best.lolicon.felis.link.LinkConfigLoader;
import best.lolicon.felis.link.LinkException;
import best.lolicon.felis.link.ModLinkPolicy;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -77,6 +78,11 @@ public final class FelisNeoForgeMod {
source.sendFailure(Component.literal("/link 只能由玩家执行 / /link can only be run by a player."));
return 0;
}
if (!source.getServer().usesAuthentication()) {
LOGGER.warn(ModLinkPolicy.OFFLINE_LOG);
source.sendFailure(Component.literal(ModLinkPolicy.OFFLINE_REPLY));
return 0;
}
requestAndReply(source.getServer(), player);
return 1;
}));
@@ -0,0 +1,24 @@
package best.lolicon.felis.link;
/**
* ModLinkPolicy is what the Fabric, Forge and NeoForge mods say when they refuse
* {@code /link}. A mod mints a link code for the UUID its server reports, and only an
* online-mode server has checked that UUID with Mojang. Behind a proxy the backend runs
* offline-mode and the proxy's own {@code /link} already serves every backend; on a
* cracked server the UUID is whatever the client claims, so a code minted there would
* let anyone link someone else's Minecraft account. The mods therefore link only on a
* standalone online-mode server.
*/
public final class ModLinkPolicy {
/** OFFLINE_REPLY is the chat line a player gets on an offline-mode server. */
public static final String OFFLINE_REPLY =
"此服务器未开启正版验证,不能在这里绑定 / This server runs with online-mode off, so /link is unavailable here.";
/** OFFLINE_LOG is the server-log line for the same refusal. */
public static final String OFFLINE_LOG =
"Felis link: refused /link because online-mode is off. The mod links only on a standalone "
+ "online-mode server; behind the Felis proxy, the proxy serves /link.";
private ModLinkPolicy() {
}
}