Skip to content

Commit bdfa59c

Browse files
committed
fix: fixed /link command was possible spam
1 parent d6bebf6 commit bdfa59c

5 files changed

Lines changed: 67 additions & 18 deletions

File tree

Pano/src/main/kotlin/com/panomc/platform/server/event/GenerateLinkCodeEvent.kt

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,51 @@ import com.panomc.platform.db.model.Server
66
import com.panomc.platform.server.ServerEvent
77
import com.panomc.platform.server.event.request.GenerateLinkCodeEventRequest
88
import com.panomc.platform.server.event.response.GenerateLinkCodeEventResponse
9-
import java.util.Random
9+
import com.panomc.platform.server.event.response.GenerateLinkCodeStatus
10+
import java.security.SecureRandom
1011

1112
@Event
12-
class GenerateLinkCodeEvent(private val databaseManager: DatabaseManager) :
13+
class GenerateLinkCodeEvent(private val databaseManager: DatabaseManager) :
1314
ServerEvent<GenerateLinkCodeEventRequest, GenerateLinkCodeEventResponse>() {
1415

15-
override suspend fun handle(request: GenerateLinkCodeEventRequest, server: Server): GenerateLinkCodeEventResponse? {
16+
companion object {
17+
// Lifetime of a generated link code in milliseconds. Must stay in sync with
18+
// VerifyLinkCodeAPI's expiry check (Calendar.SECOND, 30).
19+
private const val LINK_CODE_TTL_MS = 30_000L
20+
}
21+
22+
private val secureRandom = SecureRandom()
23+
24+
override suspend fun handle(request: GenerateLinkCodeEventRequest, server: Server): GenerateLinkCodeEventResponse {
1625
val username = request.username
1726
val sqlClient = databaseManager.getSqlClient()
1827

19-
val user = databaseManager.userDao.getByUsername(username, sqlClient) ?: return null
20-
// If user not found, return null (no response). Or strictly speaking, we could return error response if protocol allows.
21-
// But for now, silence is fine, or simple error string in code.
22-
// Or create user? No, they should join first.
28+
val user = databaseManager.userDao.getByUsername(username, sqlClient)
29+
?: return GenerateLinkCodeEventResponse(status = GenerateLinkCodeStatus.USER_NOT_FOUND)
30+
31+
// Mirror LoginAPI's LinkCodeRequired condition: /link only makes sense for users
32+
// that have neither password nor email set. Any other state means the user can
33+
// log in via the regular flow and shouldn't be issued a new link code.
34+
val hasPassword = databaseManager.userDao.hasPassword(user.id, sqlClient)
35+
val hasEmail = !user.email.isNullOrEmpty()
36+
if (hasPassword || hasEmail) {
37+
return GenerateLinkCodeEventResponse(status = GenerateLinkCodeStatus.ALREADY_REGISTERED)
38+
}
39+
40+
val now = System.currentTimeMillis()
2341

24-
val code = String.format("%06d", Random().nextInt(999999))
25-
val createdAt = System.currentTimeMillis()
42+
// Reuse an existing code while it is still valid so spamming /link does not
43+
// rotate the code that the user is currently trying to enter on the website.
44+
val existing = databaseManager.userDao.getLinkCode(username, sqlClient)
45+
val existingCode = existing?.first
46+
val existingCreatedAt = existing?.second ?: 0L
47+
if (!existingCode.isNullOrEmpty() && now - existingCreatedAt < LINK_CODE_TTL_MS) {
48+
return GenerateLinkCodeEventResponse(code = existingCode)
49+
}
2650

27-
databaseManager.userDao.setLinkCode(username, code, createdAt, sqlClient)
51+
val code = String.format("%06d", secureRandom.nextInt(1_000_000))
52+
databaseManager.userDao.setLinkCode(username, code, now, sqlClient)
2853

29-
return GenerateLinkCodeEventResponse(code)
54+
return GenerateLinkCodeEventResponse(code = code)
3055
}
3156
}

Pano/src/main/kotlin/com/panomc/platform/server/event/response/GenerateLinkCodeEventResponse.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,19 @@ package com.panomc.platform.server.event.response
22

33
import com.panomc.platform.server.ServerEventResponse
44

5-
data class GenerateLinkCodeEventResponse(val code: String) : ServerEventResponse()
5+
enum class GenerateLinkCodeStatus {
6+
// Code generated (or reused) successfully.
7+
SUCCESS,
8+
9+
// The Minecraft user already has credentials (password or email) on the website,
10+
// therefore /link is not needed.
11+
ALREADY_REGISTERED,
12+
13+
// No user with the requested username exists in the database yet.
14+
USER_NOT_FOUND
15+
}
16+
17+
data class GenerateLinkCodeEventResponse(
18+
val code: String? = null,
19+
val status: GenerateLinkCodeStatus = GenerateLinkCodeStatus.SUCCESS
20+
) : ServerEventResponse()

Pano/src/main/resources/locales/mcplugin/en-US.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"link": {
88
"code-message": "&aYour link code is: &e{{code}}\n&aIt is valid for 30 seconds.",
99
"failed": "&cFailed to generate link code. Please try again later.",
10-
"only-players": "&cOnly players can use this command."
10+
"only-players": "&cOnly players can use this command.",
11+
"already-registered": "&cYou are already registered. There is no need to use /link.",
12+
"user-not-found": "&cYour account could not be found. Please rejoin the server and try again.",
13+
"cooldown": "&ePlease wait &6{{seconds}}&e seconds before using /link again."
1114
}
1215
}
13-
}
16+
}

Pano/src/main/resources/locales/mcplugin/ru.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"link": {
88
"code-message": "&aВаш код привязки: &e{{code}}\n&aОн действителен 30 секунд.",
99
"failed": "&cНе удалось создать код привязки. Пожалуйста, попробуйте позже.",
10-
"only-players": "&cЭту команду могут использовать только игроки."
10+
"only-players": "&cЭту команду могут использовать только игроки.",
11+
"already-registered": "&cВы уже зарегистрированы. Использовать /link не нужно.",
12+
"user-not-found": "&cВаш аккаунт не найден. Пожалуйста, перезайдите на сервер и попробуйте снова.",
13+
"cooldown": "&eПодождите &6{{seconds}}&e секунд перед повторным использованием /link."
1114
}
1215
}
13-
}
16+
}

Pano/src/main/resources/locales/mcplugin/tr.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"link": {
88
"code-message": "&aBağlantı kodunuz: &e{{code}}\n&aBu kod 30 saniye geçerlidir.",
99
"failed": "&cBağlantı kodu oluşturulamadı. Lütfen daha sonra tekrar deneyin.",
10-
"only-players": "&cBu komutu sadece oyuncular kullanabilir."
10+
"only-players": "&cBu komutu sadece oyuncular kullanabilir.",
11+
"already-registered": "&cZaten kayıtlısınız. /link komutunu kullanmanıza gerek yok.",
12+
"user-not-found": "&cHesabınız bulunamadı. Lütfen sunucuya tekrar giriş yapıp deneyin.",
13+
"cooldown": "&e/link komutunu tekrar kullanmadan önce &6{{seconds}}&e saniye bekleyin."
1114
}
1215
}
13-
}
16+
}

0 commit comments

Comments
 (0)