2121
2222import javax .inject .Inject ;
2323import java .util .List ;
24+ import java .util .Locale ;
2425import java .util .Optional ;
2526import java .util .UUID ;
2627
@@ -42,6 +43,7 @@ public class BungeeReceiver implements PluginMessageListener, SettingsDependent
4243
4344 private boolean isEnabled ;
4445 private String proxySharedSecret ;
46+ private boolean channelRegistered ;
4547
4648 @ Inject
4749 BungeeReceiver (AuthMe plugin , BukkitService bukkitService , ProxySessionManager proxySessionManager ,
@@ -62,12 +64,17 @@ public void reload(Settings settings) {
6264 this .proxySharedSecret = settings .getProperty (HooksSettings .PROXY_SHARED_SECRET );
6365 this .isEnabled = settings .getProperty (HooksSettings .BUNGEECORD );
6466 final Messenger messenger = plugin .getServer ().getMessenger ();
65- if (this .isEnabled && messenger != null ) {
66- if (!messenger .isIncomingChannelRegistered (plugin , AUTHME_CHANNEL )) {
67- messenger .registerIncomingPluginChannel (plugin , AUTHME_CHANNEL , this );
68- }
69- } else if (messenger != null && messenger .isIncomingChannelRegistered (plugin , AUTHME_CHANNEL )) {
67+ if (messenger == null ) {
68+ return ;
69+ }
70+ // Track our own registration rather than querying the channel: other listeners
71+ // (e.g. the Paper configuration-phase receiver) may register the same channel.
72+ if (this .isEnabled && !channelRegistered ) {
73+ messenger .registerIncomingPluginChannel (plugin , AUTHME_CHANNEL , this );
74+ channelRegistered = true ;
75+ } else if (!this .isEnabled && channelRegistered ) {
7076 messenger .unregisterIncomingPluginChannel (plugin , AUTHME_CHANNEL , this );
77+ channelRegistered = false ;
7178 }
7279 }
7380
@@ -126,33 +133,83 @@ public void onPluginMessageReceived(String channel, Player player, byte[] data)
126133 }
127134
128135 if (type .get () == MessageType .PERFORM_LOGIN ) {
129- long timestamp ;
130- String uuidOrHmac ;
131- UUID verifiedPremiumUuid = null ;
132- String hmac ;
133- try {
134- timestamp = in .readLong ();
135- uuidOrHmac = in .readUTF ();
136- } catch (IllegalStateException e ) {
137- logger .warning ("Received perform.login without HMAC — update your proxy plugin" );
136+ VerifiedProxyLogin verified = parseAndVerifyPerformLogin (in , argument );
137+ if (verified == null ) {
138138 return ;
139139 }
140- try {
141- UUID parsedUuid = UuidUtils .parseUuidSafely (uuidOrHmac );
142- if (parsedUuid != null || uuidOrHmac .isEmpty ()) {
143- verifiedPremiumUuid = parsedUuid ;
144- hmac = in .readUTF ();
145- } else {
146- hmac = uuidOrHmac ;
147- }
148- } catch (IllegalStateException e ) {
140+ performLogin (verified .name , verified .verifiedPremiumUuid );
141+ }
142+ }
143+
144+ /**
145+ * Parses and HMAC-verifies the remainder of a {@code perform.login} message (everything after the
146+ * player-name argument).
147+ *
148+ * @param in the data input positioned right after the player-name argument
149+ * @param playerName the player-name argument that was already read
150+ * @return the verified login data, or {@code null} if the message was malformed or failed verification
151+ */
152+ private VerifiedProxyLogin parseAndVerifyPerformLogin (ByteArrayDataInput in , String playerName ) {
153+ long timestamp ;
154+ String uuidOrHmac ;
155+ UUID verifiedPremiumUuid = null ;
156+ String hmac ;
157+ try {
158+ timestamp = in .readLong ();
159+ uuidOrHmac = in .readUTF ();
160+ } catch (IllegalStateException e ) {
161+ logger .warning ("Received perform.login without HMAC — update your proxy plugin" );
162+ return null ;
163+ }
164+ try {
165+ UUID parsedUuid = UuidUtils .parseUuidSafely (uuidOrHmac );
166+ if (parsedUuid != null || uuidOrHmac .isEmpty ()) {
167+ verifiedPremiumUuid = parsedUuid ;
168+ hmac = in .readUTF ();
169+ } else {
149170 hmac = uuidOrHmac ;
150171 }
151- if (!verifyHmac (argument , timestamp , verifiedPremiumUuid , hmac )) {
152- return ;
172+ } catch (IllegalStateException e ) {
173+ hmac = uuidOrHmac ;
174+ }
175+ if (!verifyHmac (playerName , timestamp , verifiedPremiumUuid , hmac )) {
176+ return null ;
177+ }
178+ return new VerifiedProxyLogin (playerName , verifiedPremiumUuid );
179+ }
180+
181+ /**
182+ * Validates and queues a {@code perform.login} received during Paper/Folia's connection
183+ * configuration phase, when the player does not yet exist as a {@link Player}. Queuing it in the
184+ * {@link ProxySessionManager} lets the blocking pre-join login dialog be skipped (and
185+ * {@code processJoin} auto-login the player) instead of waiting for the post-join
186+ * {@code perform.login}, which arrives only after the configuration phase has completed.
187+ *
188+ * @param data the raw plugin-message payload
189+ * @return the normalized player name if this was a valid {@code perform.login}, otherwise {@code null}
190+ */
191+ public String handleConfigPhasePerformLogin (byte [] data ) {
192+ if (!isEnabled ) {
193+ return null ;
194+ }
195+ ByteArrayDataInput in = ByteStreams .newDataInput (data );
196+ String argument ;
197+ try {
198+ String typeId = in .readUTF ();
199+ if (!MessageType .PERFORM_LOGIN .getId ().equals (typeId )) {
200+ return null ;
153201 }
154- performLogin (argument , verifiedPremiumUuid );
202+ argument = in .readUTF ();
203+ } catch (IllegalStateException e ) {
204+ return null ;
205+ }
206+ VerifiedProxyLogin verified = parseAndVerifyPerformLogin (in , argument );
207+ if (verified == null ) {
208+ return null ;
155209 }
210+ proxySessionManager .processProxySessionMessage (verified .name , verified .verifiedPremiumUuid );
211+ logger .debug ("Config-phase perform.login validated and queued for {0}" , verified .name );
212+ return verified .name .toLowerCase (Locale .ROOT );
156213 }
157214
158215 private boolean verifyHmac (String playerName , long timestamp , UUID verifiedPremiumUuid , String providedHmac ) {
@@ -219,4 +276,19 @@ private void completeProxyLogin(Player player) {
219276 logger .info (player .getName () + " has been automatically logged in via proxy request." );
220277 }
221278
279+ /**
280+ * Holder for a validated {@code perform.login}: the player name and the proxy-verified premium UUID
281+ * (or {@code null} if the player is not a verified premium player).
282+ */
283+ private static final class VerifiedProxyLogin {
284+
285+ private final String name ;
286+ private final UUID verifiedPremiumUuid ;
287+
288+ private VerifiedProxyLogin (String name , UUID verifiedPremiumUuid ) {
289+ this .name = name ;
290+ this .verifiedPremiumUuid = verifiedPremiumUuid ;
291+ }
292+ }
293+
222294}
0 commit comments