Skip to content

Enforce whitelist and add command - #3467

Open
yel0h wants to merge 8 commits into
PixelGuys:masterfrom
yel0h:whitelist-command
Open

Enforce whitelist and add command#3467
yel0h wants to merge 8 commits into
PixelGuys:masterfrom
yel0h:whitelist-command

Conversation

@yel0h

@yel0h yel0h commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Enforces the player whitelist during the connection handshake and adds a /whitelist command to manage it.

After a connecting player's key is resolved, the server checks players.isAllowedToJoin when world.settings.whitelistEnabled is set, rejecting the connection with error.NotWhitelisted.

Command syntax: /whitelist <add/block> <keyType>:<base64Key> or /whitelist <add/block> @<playerIndex>

Also removes error logging for error.NotWhitelisted since it's expected to reject and already logs the reason right after rejection.

Closes #2566

@Wunka Wunka moved this to High Priority in PRs to review Aug 4, 2026

@Wunka Wunka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for continuing with this!

Comment thread src/network/protocols.zig Outdated
const keys = zon.getChild("keys");
try conn.user.?.identifyFromKeysAndName(name, keys);

if (main.server.world.?.settings.whitelistEnabled and !main.server.players.isAllowedToJoin(conn.user.?.newKeyString.?)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. In my mind this can perfectly work in sync with a ban system (blocked = banned)
I would suggest to go in the direction I went with permissions. Instead of returning in isAllowedToJoin a bool return an enum: {allowed, neutral, blocked} neutral would then be the case where you need to look if whitelist is enabled

Comment thread src/server/command/whitelist.zig Outdated
.alreadyAllowed => source.sendMessage("#ff0000{s}§#ff0000 is already on the whitelist", .{key}),
},
.block => switch (players.block(key)) {
.blocked => source.sendMessage("#00ff00Blocked {s}§#00ff00 from connecting", .{key}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go by the principle that blocked = ban then I would suggest to also kick the player here

Comment thread src/server/command/whitelist.zig Outdated
}
}

const KeyString = struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we put these kinds of helper in command.zig I can already imagine there being a command to for example update the key where this would help

@yel0h

yel0h commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

True, it does make more sense to treat an explicit block as a ban

@yel0h
yel0h force-pushed the whitelist-command branch from 3a68e6c to 49bd0fd Compare August 7, 2026 19:24
Comment thread src/network/protocols.zig Outdated
Comment thread src/server/command.zig
Comment thread src/network/protocols.zig Outdated
const keys = zon.getChild("keys");
try conn.user.?.identifyFromKeysAndName(name, keys);

switch (main.server.players.isAllowedToJoin(conn.user.?.newKeyString.?)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to have this switch and the check for whitelistEnabled inside the isAllowedToJoin function, also in my opinion the same generic message for both paths would be enough.

Comment thread src/server/server.zig Outdated
return null;
}

pub fn getUserByKey(key: []const u8) ?*User {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a one-time use case, there is no need to pollute the namespace with it, everything used inside here is already public, so please inline it to the implementation site.

Comment thread src/network.zig Outdated
if (@errorReturnTrace()) |trace| {
std.log.info("{f}", .{main.fmt.FormatErrorTrace{.stackTrace = trace.*}});
switch (err) {
error.NotWhitelisted => {},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code here should worry about the error sets of arbitrary network protocols. I assume you want to get rid of the error popup? I think the right solution here would be to change the error to a warning, there are many other cases where this code is run (e.g. version differences) that probably shouldn't have the error popup either.

Comment thread src/server/command/whitelist.zig
@IntegratedQuantum IntegratedQuantum moved this from High Priority to In review in PRs to review Aug 8, 2026
@yel0h

yel0h commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Sure, but if whitelistEnabled is supposed to be changed after world creation, and since commands can now be entered through the console for headless, I think it should be part of ServerWorld itself and not the Settings struct, whose values don't change after creation.

@IntegratedQuantum

Copy link
Copy Markdown
Member

WorldSettings will also include stuff like gamerules in the future, which I would also like to have configurable in-game.

@Wunka

Wunka commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Sure, but if whitelistEnabled is supposed to be changed after world creation, and since commands can now be entered through the console for headless, I think it should be part of ServerWorld itself and not the Settings struct, whose values don't change after creation.

Actually they will change: #3499 (maybe not from within the world, but that can also change)

@yel0h

yel0h commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Ahh okay. Fine to save like this then?

applyAction(source, params.action, key);
},
.@"/whitelist <enable/disable>" => |params| {
main.server.world.?.settings.whitelistEnabled.store(params.toggle == .enable, .monotonic);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change also needs to be stored to disk immediately to avoid losing it when the game doesn't close correctly. (world.saveWorldConfig)

Comment thread src/network/protocols.zig Outdated
const keys = zon.getChild("keys");
try conn.user.?.identifyFromKeysAndName(name, keys);

if (!main.server.players.isAllowedToJoin(conn.user.?.newKeyString.?, main.server.world.?.settings.whitelistEnabled.load(.monotonic))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem with this. There are multiple keys in order to allow switching to a new crypto algorithm when one is broken. So when the server switches this (for testing you can do this in the launchConfig), it will then reject the old player which is still in their list with one of the old keys.

To fix this I think the best solution would be to do this check together with players.lookupIndex in User.identifyFromKeysAndName, so that the old entries are found correctly.

@yel0h

yel0h commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Ok, I merged players.isAllowedToJoin into lookupIndex, works with algorithm switching now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Player whitelist

3 participants