diff --git a/build.gradle b/build.gradle index ccc025eb..afbb5ed1 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ publishing { javadoc { getOptions().setEncoding("UTF-8") getOptions().setCharSet("UTF-8") - getOptions().setSource("11") + getOptions().setSource("17") getOptions().links("https://docs.oracle.com/en/java/javase/11/docs/api/") // Remove "undefined" from search paths when generating javadoc for a non-modular project. (JDK-8215291) diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index 5425d0ec..478a63dd 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -108,6 +108,7 @@ import net.elytrium.limboauth.floodgate.FloodgateApiHolder; import net.elytrium.limboauth.handler.AuthSessionHandler; import net.elytrium.limboauth.listener.AuthListener; +import net.elytrium.limboauth.listener.BackendEndpointsListener; import net.elytrium.limboauth.model.RegisteredPlayer; import net.elytrium.limboauth.model.SQLRuntimeException; import net.kyori.adventure.text.Component; @@ -405,6 +406,11 @@ public void reload() { EventManager eventManager = this.server.getEventManager(); eventManager.unregisterListeners(this); eventManager.register(this, new AuthListener(this, this.playerDao, this.floodgateApi)); + if (Settings.IMP.MAIN.BACKEND_API.ENABLED) { + eventManager.register(this, new BackendEndpointsListener(this)); + } else { + this.server.getChannelRegistrar().unregister(BackendEndpointsListener.API_CHANNEL); + } if (this.purgeCacheTask != null) { this.purgeCacheTask.cancel(); diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java index 75aa766f..02feaef9 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -306,6 +306,27 @@ public Title.Times toTimes() { } } + @Create + public Settings.MAIN.BACKEND_API BACKEND_API; + + public static class BACKEND_API { + + @Comment({ + "Should backend API be enabled?", + "Required for PlaceholderAPI expansion to work (https://github.com/UserNugget/LimboAuth-Expansion)" + }) + public boolean ENABLED = true; + + @Comment({ + "Available endpoints:", + " premium_state, hash, totp_token, login_date, reg_date, token_issued_at,", + " uuid, premium_uuid, ip, login_ip, token_issued_at" + }) + public List ENABLED_ENDPOINTS = List.of( + "premium_state", "login_date", "reg_date", "uuid", "premium_uuid", "token_issued_at" + ); + } + @Create public MAIN.COMMAND_PERMISSION_STATE COMMAND_PERMISSION_STATE; diff --git a/src/main/java/net/elytrium/limboauth/backend/Endpoint.java b/src/main/java/net/elytrium/limboauth/backend/Endpoint.java new file mode 100644 index 00000000..2f5d93ea --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/Endpoint.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import net.elytrium.limboauth.LimboAuth; + +public abstract class Endpoint { + + protected final LimboAuth plugin; + + public Endpoint(LimboAuth plugin) { + this.plugin = plugin; + } + + public abstract void write(ByteArrayDataOutput output); + + public abstract void read(ByteArrayDataInput input); +} diff --git a/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java new file mode 100644 index 00000000..7ccf60a2 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend.type; + +import java.util.function.Function; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.RegisteredPlayer; + +public class LongDatabaseEndpoint extends LongEndpoint { + + public LongDatabaseEndpoint(LimboAuth plugin, String type, String username, long value) { + super(plugin, type, username, value); + } + + public LongDatabaseEndpoint(LimboAuth plugin, String type, Function function) { + super(plugin, type, username -> { + RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerDao(), username); + if (player == null) { + return Long.MIN_VALUE; + } else { + return function.apply(player); + } + }); + } +} diff --git a/src/main/java/net/elytrium/limboauth/backend/type/LongEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/LongEndpoint.java new file mode 100644 index 00000000..827e96af --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/type/LongEndpoint.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend.type; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import java.util.function.Function; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.Settings; +import net.elytrium.limboauth.backend.Endpoint; + +public class LongEndpoint extends Endpoint { + + private String type; + private Function function; + private String username; + private long value; + + public LongEndpoint(LimboAuth plugin, String type, Function function) { + super(plugin); + this.type = type; + this.function = function; + } + + public LongEndpoint(LimboAuth plugin, String type, String username, long value) { + super(plugin); + this.type = type; + this.username = username; + this.value = value; + } + + @Override + public void write(ByteArrayDataOutput output) { + output.writeUTF(this.type); + if (!Settings.IMP.MAIN.BACKEND_API.ENABLED_ENDPOINTS.contains(this.type)) { + output.writeInt(-1); + output.writeUTF(this.username); + return; + } + + output.writeInt(0); + output.writeUTF(this.username); + output.writeLong(this.value); + } + + @Override + public void read(ByteArrayDataInput input) { + int version = input.readInt(); + if (version != 0) { + throw new IllegalStateException("unsupported '" + this.type + "' endpoint version: " + version); + } + + this.username = input.readUTF(); + this.value = this.function.apply(this.username); + } + + @Override + public String toString() { + return "LongEndpoint{" + + "username='" + this.username + '\'' + + ", value=" + this.value + + '}'; + } +} diff --git a/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java new file mode 100644 index 00000000..174a8d4b --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend.type; + +import java.util.function.Function; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.RegisteredPlayer; + +public class StringDatabaseEndpoint extends StringEndpoint { + + public StringDatabaseEndpoint(LimboAuth plugin, String type, String username, String value) { + super(plugin, type, username, value); + } + + public StringDatabaseEndpoint(LimboAuth plugin, String type, Function function) { + super(plugin, type, username -> { + RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerDao(), username); + if (player == null) { + return ""; + } else { + return function.apply(player); + } + }); + } +} diff --git a/src/main/java/net/elytrium/limboauth/backend/type/StringEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/StringEndpoint.java new file mode 100644 index 00000000..980aa81c --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/type/StringEndpoint.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend.type; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import java.util.function.Function; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.Settings; +import net.elytrium.limboauth.backend.Endpoint; + +public class StringEndpoint extends Endpoint { + + private String type; + private Function function; + private String username; + private String value; + + public StringEndpoint(LimboAuth plugin, String type, Function function) { + super(plugin); + this.type = type; + this.function = function; + } + + public StringEndpoint(LimboAuth plugin, String type, String username, String value) { + super(plugin); + this.type = type; + this.username = username; + this.value = value; + } + + @Override + public void write(ByteArrayDataOutput output) { + output.writeUTF(this.type); + if (!this.type.equals("available_endpoints") && !Settings.IMP.MAIN.BACKEND_API.ENABLED_ENDPOINTS.contains(this.type)) { + output.writeInt(-1); + output.writeUTF(this.username); + return; + } + + output.writeInt(0); + output.writeUTF(this.username); + output.writeUTF(this.value); + } + + @Override + public void read(ByteArrayDataInput input) { + int version = input.readInt(); + if (version != 0) { + throw new IllegalStateException("unsupported '" + this.type + "' endpoint version: " + version); + } + + this.username = input.readUTF(); + this.value = this.function.apply(this.username); + } + + @Override + public String toString() { + return "StringEndpoint{" + + "username='" + this.username + '\'' + + ", value=" + this.value + + '}'; + } +} diff --git a/src/main/java/net/elytrium/limboauth/backend/type/UnknownEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/UnknownEndpoint.java new file mode 100644 index 00000000..d208f4c2 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/backend/type/UnknownEndpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.backend.type; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.backend.Endpoint; + +public class UnknownEndpoint extends Endpoint { + + private String type; + + public UnknownEndpoint(LimboAuth plugin) { + super(plugin); + } + + public UnknownEndpoint(LimboAuth plugin, String type) { + super(plugin); + this.type = type; + } + + @Override + public void write(ByteArrayDataOutput output) { + output.writeUTF(this.type); + output.writeInt(-2); + } + + @Override + public void read(ByteArrayDataInput input) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "UnknownEndpoint{" + + "type='" + this.type + '\'' + + '}'; + } +} diff --git a/src/main/java/net/elytrium/limboauth/listener/BackendEndpointsListener.java b/src/main/java/net/elytrium/limboauth/listener/BackendEndpointsListener.java new file mode 100644 index 00000000..0aa4c70c --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/listener/BackendEndpointsListener.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboauth.listener; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import java.util.AbstractMap.SimpleEntry; +import java.util.Map; +import java.util.function.Function; +import net.elytrium.limboauth.LimboAuth; +import net.elytrium.limboauth.Settings; +import net.elytrium.limboauth.backend.Endpoint; +import net.elytrium.limboauth.backend.type.LongDatabaseEndpoint; +import net.elytrium.limboauth.backend.type.StringDatabaseEndpoint; +import net.elytrium.limboauth.backend.type.StringEndpoint; +import net.elytrium.limboauth.backend.type.UnknownEndpoint; +import net.elytrium.limboauth.model.RegisteredPlayer; + +public class BackendEndpointsListener { + + public static final ChannelIdentifier API_CHANNEL = MinecraftChannelIdentifier.create("limboauth", "backend_api"); + + public static final Map> TYPES = Map.ofEntries( + new SimpleEntry<>("available_endpoints", plugin -> new StringEndpoint(plugin, "available_endpoints", + username -> String.join(",", Settings.IMP.MAIN.BACKEND_API.ENABLED_ENDPOINTS))), + new SimpleEntry<>("premium_state", lauth -> new StringEndpoint(lauth, "premium_state", + username -> lauth.isPremiumInternal(username).getState().name())), + new SimpleEntry<>("hash", plugin -> new StringDatabaseEndpoint(plugin, "hash", RegisteredPlayer::getHash)), + new SimpleEntry<>("totp_token", plugin -> new StringDatabaseEndpoint(plugin, "totp_token", RegisteredPlayer::getTotpToken)), + new SimpleEntry<>("reg_date", plugin -> new LongDatabaseEndpoint(plugin, "reg_date", RegisteredPlayer::getRegDate)), + new SimpleEntry<>("uuid", plugin -> new StringDatabaseEndpoint(plugin, "uuid", RegisteredPlayer::getUuid)), + new SimpleEntry<>("premium_uuid", plugin -> new StringDatabaseEndpoint(plugin, "premium_uuid", RegisteredPlayer::getPremiumUuid)), + new SimpleEntry<>("ip", plugin -> new StringDatabaseEndpoint(plugin, "ip", RegisteredPlayer::getIP)), + new SimpleEntry<>("login_ip", plugin -> new StringDatabaseEndpoint(plugin, "login_ip", RegisteredPlayer::getLoginIp)), + new SimpleEntry<>("login_date", plugin -> new LongDatabaseEndpoint(plugin, "login_date", RegisteredPlayer::getLoginDate)), + new SimpleEntry<>("token_issued_at", plugin -> new LongDatabaseEndpoint(plugin, "token_issued_at", RegisteredPlayer::getTokenIssuedAt)) + ); + + private final LimboAuth plugin; + + public BackendEndpointsListener(LimboAuth plugin) { + this.plugin = plugin; + + plugin.getServer().getChannelRegistrar().register(API_CHANNEL); + } + + @Subscribe + public void onRequest(PluginMessageEvent event) { + if (event.getIdentifier() != API_CHANNEL || !(event.getSource() instanceof ServerConnection server)) { + return; + } + + ByteArrayDataInput in = ByteStreams.newDataInput(event.getData()); + String dataType = in.readUTF(); + Function typeFunc = TYPES.get(dataType); + if (typeFunc == null) { + this.send(server, new UnknownEndpoint(this.plugin, dataType)); + } else { + Endpoint endpoint = typeFunc.apply(this.plugin); + endpoint.read(in); + this.send(server, endpoint); + } + } + + private void send(ChannelMessageSink sink, Endpoint endpoint) { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + endpoint.write(output); + sink.sendPluginMessage(API_CHANNEL, output.toByteArray()); + } +}