Skip to content

Commit c924ad0

Browse files
authored
fix: proxy support (#24)
1 parent 0657b61 commit c924ad0

125 files changed

Lines changed: 42204 additions & 310 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e.yml

Lines changed: 82 additions & 277 deletions
Large diffs are not rendered by default.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ e2e/tests/*.log
3131
e2e/tests/coverage/
3232
e2e/jars/
3333

34+
# E2E runtime-generated files (LuckPerms libs, plugin data, etc.)
35+
e2e/platforms/**/luckperms/libs/
36+
e2e/platforms/**/luckperms/*.json
37+
3438
dependency-reduced-pom.xml
3539

3640
# Fabric/Loom

bungee/build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ tasks.named<ShadowJar>("shadowJar") {
6060
dependencies {
6161
include(dependency(":BanManagerWebEnhancerCommon"))
6262
include(dependency(":BanManagerWebEnhancerLibs"))
63-
relocate("org.bstats", "me.confuser.banmanager.webenhancer.common.bstats") {
64-
include(dependency("org.bstats:"))
65-
}
63+
include(dependency("org.bstats:bstats-bungeecord:.*"))
64+
include(dependency("org.bstats:bstats-base:.*"))
6665
}
6766

67+
relocate("org.bstats", "me.confuser.banmanager.webenhancer.common.bstats")
68+
6869
exclude("GradleStart**")
6970
exclude(".cache");
7071
exclude("LICENSE*")

common/build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
`java-library`
3+
jacoco
34
}
45

56
applyPlatformAndCoreConfiguration()
@@ -23,4 +24,18 @@ tasks.withType<Test>().configureEach {
2324
useJUnit()
2425
maxHeapSize = "512m"
2526
forkEvery = 1 // Fork a new JVM for each test class to prevent memory accumulation
27+
finalizedBy(tasks.jacocoTestReport)
28+
}
29+
30+
jacoco {
31+
toolVersion = "0.8.11"
32+
}
33+
34+
tasks.jacocoTestReport {
35+
dependsOn(tasks.test)
36+
reports {
37+
xml.required.set(true)
38+
html.required.set(true)
39+
html.outputLocation.set(layout.buildDirectory.dir("reports/jacoco"))
40+
}
2641
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package me.confuser.banmanager.webenhancer.common.data;
2+
3+
import me.confuser.banmanager.common.data.PlayerData;
4+
import org.junit.Test;
5+
6+
import java.security.NoSuchAlgorithmException;
7+
import java.util.UUID;
8+
9+
import static org.junit.Assert.*;
10+
import static org.mockito.Mockito.*;
11+
12+
/**
13+
* Unit tests for PlayerPinData.
14+
*/
15+
public class PlayerPinDataTest {
16+
17+
@Test
18+
public void shouldGenerateSixDigitPin() throws NoSuchAlgorithmException {
19+
PlayerData player = mock(PlayerData.class);
20+
when(player.getUUID()).thenReturn(UUID.randomUUID());
21+
22+
PlayerPinData pinData = new PlayerPinData(player);
23+
int pin = pinData.getGeneratedPin();
24+
25+
// 6 digit pin: 100000-999999
26+
assertTrue("Pin should be at least 100000", pin >= 100000);
27+
assertTrue("Pin should be at most 999999", pin <= 999999);
28+
}
29+
30+
@Test
31+
public void shouldHashPinWithArgon2() throws NoSuchAlgorithmException {
32+
PlayerData player = mock(PlayerData.class);
33+
when(player.getUUID()).thenReturn(UUID.randomUUID());
34+
35+
PlayerPinData pinData = new PlayerPinData(player);
36+
String hashedPin = pinData.getPin();
37+
38+
assertNotNull("Hashed pin should not be null", hashedPin);
39+
assertTrue("Pin should be hashed with Argon2i", hashedPin.startsWith("$argon2i$"));
40+
}
41+
42+
@Test
43+
public void shouldSetExpiryFiveMinutesFromNow() throws NoSuchAlgorithmException {
44+
PlayerData player = mock(PlayerData.class);
45+
when(player.getUUID()).thenReturn(UUID.randomUUID());
46+
47+
long beforeCreate = System.currentTimeMillis() / 1000L;
48+
PlayerPinData pinData = new PlayerPinData(player);
49+
long afterCreate = System.currentTimeMillis() / 1000L;
50+
51+
// Expiry should be 300 seconds (5 minutes) from creation
52+
long expires = pinData.getExpires();
53+
54+
// Allow 1 second tolerance for test execution
55+
assertTrue("Expiry should be ~300 seconds from creation",
56+
expires >= beforeCreate + 299 && expires <= afterCreate + 301);
57+
}
58+
59+
@Test
60+
public void shouldStoreGeneratedPinForDisplay() throws NoSuchAlgorithmException {
61+
PlayerData player = mock(PlayerData.class);
62+
when(player.getUUID()).thenReturn(UUID.randomUUID());
63+
64+
PlayerPinData pinData = new PlayerPinData(player);
65+
66+
int generatedPin = pinData.getGeneratedPin();
67+
String hashedPin = pinData.getPin();
68+
69+
assertNotEquals("Generated pin should not be 0", 0, generatedPin);
70+
assertFalse("Hashed pin should not equal plaintext",
71+
hashedPin.equals(String.valueOf(generatedPin)));
72+
}
73+
74+
@Test
75+
public void shouldStorePlayerReference() throws NoSuchAlgorithmException {
76+
PlayerData player = mock(PlayerData.class);
77+
when(player.getUUID()).thenReturn(UUID.randomUUID());
78+
79+
PlayerPinData pinData = new PlayerPinData(player);
80+
81+
assertSame("Player reference should be stored", player, pinData.getPlayer());
82+
}
83+
84+
@Test
85+
public void shouldHaveZeroIdBeforePersistence() throws NoSuchAlgorithmException {
86+
PlayerData player = mock(PlayerData.class);
87+
when(player.getUUID()).thenReturn(UUID.randomUUID());
88+
89+
PlayerPinData pinData = new PlayerPinData(player);
90+
91+
assertEquals("ID should be 0 before persistence", 0, pinData.getId());
92+
}
93+
94+
@Test
95+
public void shouldGenerateRandomPins() throws NoSuchAlgorithmException {
96+
PlayerData player = mock(PlayerData.class);
97+
when(player.getUUID()).thenReturn(UUID.randomUUID());
98+
99+
PlayerPinData pin1 = new PlayerPinData(player);
100+
PlayerPinData pin2 = new PlayerPinData(player);
101+
PlayerPinData pin3 = new PlayerPinData(player);
102+
103+
boolean atLeastTwoDifferent =
104+
pin1.getGeneratedPin() != pin2.getGeneratedPin() ||
105+
pin1.getGeneratedPin() != pin3.getGeneratedPin() ||
106+
pin2.getGeneratedPin() != pin3.getGeneratedPin();
107+
108+
assertTrue("At least two of three pins should differ", atLeastTwoDifferent);
109+
}
110+
111+
@Test
112+
public void shouldGenerateUniqueHashesForSamePin() throws NoSuchAlgorithmException {
113+
PlayerData player = mock(PlayerData.class);
114+
when(player.getUUID()).thenReturn(UUID.randomUUID());
115+
116+
PlayerPinData pin1 = new PlayerPinData(player);
117+
PlayerPinData pin2 = new PlayerPinData(player);
118+
119+
// Even if by chance the generated pins are the same, the hashes should differ
120+
// due to unique random salts in Argon2
121+
assertNotEquals("Hashes should differ due to random salts",
122+
pin1.getPin(), pin2.getPin());
123+
}
124+
125+
@Test
126+
public void generatedPinShouldBeSettable() throws NoSuchAlgorithmException {
127+
PlayerData player = mock(PlayerData.class);
128+
when(player.getUUID()).thenReturn(UUID.randomUUID());
129+
130+
PlayerPinData pinData = new PlayerPinData(player);
131+
pinData.setGeneratedPin(999999);
132+
133+
assertEquals("Generated pin should be settable", 999999, pinData.getGeneratedPin());
134+
}
135+
}

common/src/test/java/me/confuser/banmanager/webenhancer/common/listeners/CommonPlayerDeniedListenerTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,8 @@ public void handlePin_replacesPlaceholderWithPin() {
4747
Message message = mock(Message.class);
4848
when(message.toString()).thenReturn("Your login pin is: [pin]");
4949

50-
// Execute
5150
listener.handlePin(player, message);
5251

53-
// Verify - message.set() should be called with the pin
5452
verify(message).set("pin", "123456");
5553
}
5654

@@ -60,13 +58,9 @@ public void handlePin_ignoresMessagesWithoutPlaceholder() {
6058
Message message = mock(Message.class);
6159
when(message.toString()).thenReturn("You have been banned!");
6260

63-
// Execute
6461
listener.handlePin(player, message);
6562

66-
// Verify - getValidPin should NOT be called since message doesn't contain [pin]
6763
verify(playerPinStorage, never()).getValidPin(any());
68-
69-
// Verify set was never called
7064
verify(message, never()).set(anyString(), anyString());
7165
}
7266

@@ -79,10 +73,8 @@ public void handlePin_handlesNullPinGracefully() {
7973
Message message = mock(Message.class);
8074
when(message.toString()).thenReturn("Your login pin is: [pin]");
8175

82-
// Execute - should not throw
8376
listener.handlePin(player, message);
8477

85-
// Verify - set should NOT be called when pin is null
8678
verify(message, never()).set(anyString(), anyString());
8779
}
8880
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package me.confuser.banmanager.webenhancer.common.security;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
6+
import static org.junit.Assert.*;
7+
8+
/**
9+
* Unit tests for Argon2PasswordEncoder.
10+
*/
11+
public class Argon2PasswordEncoderTest {
12+
13+
private Argon2PasswordEncoder encoder;
14+
15+
@Before
16+
public void setUp() {
17+
encoder = new Argon2PasswordEncoder();
18+
}
19+
20+
@Test
21+
public void shouldEncodePassword() {
22+
String password = "123456";
23+
24+
String encoded = encoder.encode(password);
25+
26+
assertNotNull("Encoded password should not be null", encoded);
27+
assertNotEquals("Encoded password should differ from original", password, encoded);
28+
}
29+
30+
@Test
31+
public void shouldProduceArgon2iFormat() {
32+
String password = "testpassword";
33+
34+
String encoded = encoder.encode(password);
35+
36+
// Argon2i format starts with $argon2i$
37+
assertTrue("Hash should use Argon2i format", encoded.startsWith("$argon2i$"));
38+
}
39+
40+
@Test
41+
public void shouldGenerateUniqueSaltsForSameInput() {
42+
String password = "samepassword";
43+
44+
String encoded1 = encoder.encode(password);
45+
String encoded2 = encoder.encode(password);
46+
47+
assertNotEquals("Hashes should differ due to unique salts", encoded1, encoded2);
48+
}
49+
50+
@Test
51+
public void shouldProduceCorrectHashLength() {
52+
String password = "test";
53+
54+
String encoded = encoder.encode(password);
55+
56+
assertTrue("Hash string should be substantial", encoded.length() > 50);
57+
}
58+
59+
@Test
60+
public void shouldHandleEmptyPassword() {
61+
String password = "";
62+
63+
String encoded = encoder.encode(password);
64+
65+
assertNotNull("Should handle empty password", encoded);
66+
assertTrue("Should still produce Argon2i format", encoded.startsWith("$argon2i$"));
67+
}
68+
69+
@Test
70+
public void shouldHandleLongPassword() {
71+
StringBuilder longPassword = new StringBuilder();
72+
for (int i = 0; i < 1000; i++) {
73+
longPassword.append("a");
74+
}
75+
76+
String encoded = encoder.encode(longPassword.toString());
77+
78+
assertNotNull("Should handle long password", encoded);
79+
assertTrue("Should still produce Argon2i format", encoded.startsWith("$argon2i$"));
80+
}
81+
82+
@Test
83+
public void shouldHandleSpecialCharacters() {
84+
String password = "p@$$w0rd!#$%^&*()日本語";
85+
86+
String encoded = encoder.encode(password);
87+
88+
assertNotNull("Should handle special characters", encoded);
89+
assertTrue("Should still produce Argon2i format", encoded.startsWith("$argon2i$"));
90+
}
91+
92+
@Test
93+
public void shouldHandleNumericPin() {
94+
// This is the most common use case - 6 digit PINs
95+
String pin = "123456";
96+
97+
String encoded = encoder.encode(pin);
98+
99+
assertNotNull("Should handle numeric PIN", encoded);
100+
assertTrue("Should produce Argon2i format", encoded.startsWith("$argon2i$"));
101+
102+
// Verify format contains expected parameters
103+
assertTrue("Should contain version", encoded.contains("v="));
104+
assertTrue("Should contain memory parameter", encoded.contains("m="));
105+
assertTrue("Should contain time parameter", encoded.contains("t="));
106+
assertTrue("Should contain parallelism parameter", encoded.contains("p="));
107+
}
108+
109+
@Test
110+
public void shouldProduceConsistentFormatAcrossMultipleCalls() {
111+
Argon2PasswordEncoder encoder2 = new Argon2PasswordEncoder();
112+
113+
String encoded1 = encoder.encode("test");
114+
String encoded2 = encoder2.encode("test");
115+
116+
String format1 = encoded1.substring(0, encoded1.indexOf("$", 9));
117+
118+
String format2 = encoded2.substring(0, encoded2.indexOf("$", 9));
119+
120+
assertEquals("Both instances should produce same format", format1, format2);
121+
}
122+
}

0 commit comments

Comments
 (0)