Skip to content

Commit 4379d65

Browse files
add validation for inputs in webpage
1 parent a61ea1a commit 4379d65

4 files changed

Lines changed: 63 additions & 29 deletions

File tree

src/main/kotlin/com/viaversion/aas/web/WebLogin.kt

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,20 @@ class WebLogin : WebState {
6666
}
6767
}
6868

69+
private fun validateUsername(name: String) {
70+
if (name.length > 16) throw StacklessException("Username is too long")
71+
}
72+
6973
private suspend fun handleOfflineLogin(webClient: WebClient, msg: String, obj: JsonObject) {
7074
if (!sha512Hex(msg.encodeToByteArray()).startsWith("00000")) throw StacklessException("PoW failed")
7175
if (abs(obj["date"].asLong - System.currentTimeMillis()) > Duration.ofSeconds(20).toMillis()) {
7276
throw StacklessException("Invalid PoW date")
7377
}
74-
if (obj["challenge"].asString != webClient.challenge) throw StacklessException("Invalid challenge")
78+
if (obj["challenge"].asString != webClient.challenge) {
79+
throw StacklessException("Invalid challenge")
80+
}
7581
val username = obj["username"].asString
82+
validateUsername(username)
7683
val uuid = generateOfflinePlayerUuid(username)
7784

7885
val token = webClient.server.generateToken(uuid, username)
@@ -83,6 +90,7 @@ class WebLogin : WebState {
8390

8491
private suspend fun handleTempCodeLogin(webClient: WebClient, obj: JsonObject) {
8592
val username = obj["username"].asString
93+
validateUsername(username)
8694
val code = obj["code"].asString
8795

8896
val check = webClient.server.checkTempCode(username, code)
@@ -137,31 +145,48 @@ class WebLogin : WebState {
137145
webClient.server.completeSessionHashCallback(hash)
138146
}
139147

148+
private fun parseVersion(versionString: String): ProtocolVersion {
149+
if (versionString.length > 20) throw IllegalArgumentException("Version is too long")
150+
return Ints.tryParse(versionString)?.let { ProtocolVersion.getProtocol(it) }
151+
?: ProtocolVersion.getClosest(versionString) ?: AUTO
152+
}
153+
154+
private fun parseHostAndPort(host: String, port: Int): HostAndPort {
155+
if (host.length > 255) {
156+
throw IllegalArgumentException("Host too long")
157+
}
158+
return HostAndPort.fromParts(host, port)
159+
}
160+
140161
private fun handleParametersResponse(webClient: WebClient, obj: JsonObject) {
141162
val callback = UUID.fromString(obj["callback"].asString)
163+
val backName = obj["backName"]?.asString
164+
if (backName != null) validateUsername(backName)
165+
142166
webClient.server.completeAddressCallback(
143167
callback, AddressInfo(
144-
backVersion = obj["version"].asString.let {
145-
Ints.tryParse(it)?.let { ProtocolVersion.getProtocol(it) }
146-
?: ProtocolVersion.getClosest(it) ?: AUTO
147-
},
148-
backHostAndPort = HostAndPort.fromParts(obj["host"].asString, obj["port"].asInt),
168+
backVersion = parseVersion(obj["version"].asString),
169+
backHostAndPort = parseHostAndPort(obj["host"].asString, obj["port"].asInt),
149170
frontOnline = obj["frontOnline"].asString.toBooleanStrictOrNull(),
150-
backName = obj["backName"]?.asString
171+
backName = backName
151172
)
152173
)
153174
}
154175

155-
private suspend fun handleSaveAccessToken(webClient: WebClient, obj: JsonObject) {
156-
val accessToken = obj["mc_access_token"].asString
157-
val decodedToken = JWT.decode(accessToken)
176+
private fun verifyTokenTime(notBefore: Instant, expiration: Instant) {
158177
val now = Instant.now()
159-
if (now > decodedToken.expiresAtAsInstant) {
178+
if (now > expiration) {
160179
throw IllegalArgumentException("mc access token has expired")
161180
}
162-
if (now < decodedToken.notBeforeAsInstant) {
181+
if (now < notBefore) {
163182
throw IllegalArgumentException("mc access token notBefore is in the future")
164183
}
184+
}
185+
186+
private suspend fun handleSaveAccessToken(webClient: WebClient, obj: JsonObject) {
187+
val accessToken = obj["mc_access_token"].asString
188+
val decodedToken = JWT.decode(accessToken)
189+
verifyTokenTime(decodedToken.notBeforeAsInstant, decodedToken.expiresAtAsInstant)
165190
val expectedId = UUID.fromString(decodedToken.getClaim("profiles").asMap()["mc"].toString())
166191

167192
val profile = AspirinServer.httpClient.get("https://api.minecraftservices.com/minecraft/profile") {

src/main/resources/web/index.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@
105105
<form class="input-group mb-3 row g-3 justify-content-md-center" id="address_info_form">
106106
<div class="col-md-3">
107107
<input id="connect_address" type="text" class="form-control" placeholder="Address:Port"
108-
aria-label="Address and Port">
108+
aria-label="Address and Port" required maxlength="200">
109109
</div>
110110
<div class="col-md-2">
111111
<div class="input-group">
112112
<span class="input-group-text"><abbr title="Version">v</abbr></span>
113113
<input id="connect_version" type="text" class="form-control col-auto" placeholder="Version"
114-
aria-label="Version" list="backend_version_list">
114+
aria-label="Version" list="backend_version_list" maxlength="20">
115115
</div>
116116
<datalist id="backend_version_list">
117117
<option>AUTO</option>
@@ -189,7 +189,7 @@
189189
<div class="input-group">
190190
<span class="input-group-text">.</span>
191191
<input id="instance_suffix" type="text" class="form-control"
192-
placeholder="Instance Suffix" aria-label="Instance Suffix" disabled readonly>
192+
placeholder="Instance Suffix" aria-label="Instance Suffix" disabled readonly required maxlength="64">
193193
</div>
194194
</div>
195195
</form>
@@ -347,7 +347,7 @@ <h5 class="modal-title" id="listenModalLabel">Listen to logins</h5>
347347
<p>The instance will send auth requests and address parameters to this page.</p>
348348
<div class="mb-3">
349349
<label for="listen_username" class="form-label">Username</label>
350-
<input type="text" class="form-control" id="listen_username">
350+
<input type="text" class="form-control" id="listen_username" required maxlength="16">
351351
</div>
352352
<div class="mb-3 form-check">
353353
<input type="checkbox" class="form-check-input" id="listen_online" checked>
@@ -356,18 +356,18 @@ <h5 class="modal-title" id="listenModalLabel">Listen to logins</h5>
356356
<div class="mb-3" id="listen_code_div">
357357
<label for="listen_code" class="form-label">Temp Code</label>
358358
<p>Join <span id="link_address" class="font-monospace"></span> in game to get the temporary code</p>
359-
<input type="text" class="form-control" id="listen_code">
359+
<input type="text" class="form-control" id="listen_code" maxlength="7">
360360
</div>
361361
</div>
362362
<div class="modal-footer">
363-
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal">Listen</button>
363+
<button type="submit" class="btn btn-primary">Listen</button>
364364
</div>
365365
</div>
366366
</form>
367367
</div>
368368
</div>
369369

370-
<div aria-hidden="true" aria-labelledby="listenModalLabel" class="modal fade" id="sendTokenModal" tabindex="-1">
370+
<div aria-hidden="true" aria-labelledby="sendTokenModalLabel" class="modal fade" id="sendTokenModal" tabindex="-1">
371371
<div class="modal-dialog modal-dialog-scrollable">
372372
<form id="form_send_token">
373373
<div class="modal-content">
@@ -381,13 +381,13 @@ <h5 class="modal-title" id="sendTokenModalLabel">Save Access Token in Instance</
381381
<p>You'll need to connect as online-mode in the front-end.</p>
382382
<div class="input-group mb-3">
383383
<label class="input-group-text" for="send_token_user">Account</label>
384-
<select class="form-select" id="send_token_user">
385-
<option selected>Choose...</option>
384+
<select class="form-select" id="send_token_user" required>
385+
<option value="" selected>Choose...</option>
386386
</select>
387387
</div>
388388
</div>
389389
<div class="modal-footer">
390-
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal">Send</button>
390+
<button type="submit" class="btn btn-primary">Send</button>
391391
</div>
392392
</div>
393393
</form>

src/main/resources/web/js/page.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ let accounts = document.getElementById("accounts-list");
66
let cors_proxy_txt = document.getElementById("cors-proxy");
77
let ws_url_txt = document.getElementById("ws-url");
88
let instance_suffix_input = document.getElementById("instance_suffix");
9+
let addressInfoForm = document.getElementById("address_info_form");
910
let listenVisible = false;
1011
let deltaTime = 0;
1112
let challenge = "";
@@ -109,8 +110,10 @@ function copyGeneratedAddress() {
109110
navigator.clipboard.writeText($("#generated_address").text()).catch(e => console.log(e));
110111
}
111112
function generateAddress() {
112-
let backAddress = $("#connect_address").val();
113113
try {
114+
if (!addressInfoForm.checkValidity())
115+
throw Error("invalid input");
116+
let backAddress = $("#connect_address").val();
114117
let url = new URL("https://" + backAddress);
115118
let finalAddress = "";
116119
let host = url.hostname;
@@ -142,6 +145,7 @@ function submittedListen() {
142145
let user = $("#listen_username").val().trim();
143146
if (!user)
144147
return;
148+
bootstrap.Modal.getInstance("#listenModal").hide();
145149
if ($("#listen_online")[0].checked) {
146150
sendSocket(JSON.stringify({
147151
action: "temp_code_login",
@@ -162,6 +166,7 @@ function submittedListen() {
162166
}
163167
}
164168
function submittedSendToken() {
169+
bootstrap.Modal.getInstance("#sendTokenModal").hide();
165170
let account = findAccountByMcName($("#send_token_user").val());
166171
if (!account)
167172
return;

src/main/typescript/web/js/page.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// Note that some APIs only work on HTTPS
33

44
// Page
5-
let connectionStatus = document.getElementById("connection_status")!!;
6-
let corsStatus = document.getElementById("cors_status")!!;
7-
let listening = document.getElementById("listening")!!;
8-
let accounts = document.getElementById("accounts-list")!!;
5+
let connectionStatus = document.getElementById("connection_status")!;
6+
let corsStatus = document.getElementById("cors_status")!;
7+
let listening = document.getElementById("listening")!;
8+
let accounts = document.getElementById("accounts-list")!;
99
let cors_proxy_txt = document.getElementById("cors-proxy") as HTMLInputElement;
1010
let ws_url_txt = document.getElementById("ws-url") as HTMLInputElement;
1111
let instance_suffix_input = document.getElementById("instance_suffix") as HTMLInputElement;
12+
let addressInfoForm = document.getElementById("address_info_form") as HTMLFormElement
1213
let listenVisible = false;
1314
// + deltaTime means that the clock is ahead
1415
let deltaTime = 0;
@@ -127,8 +128,9 @@ function copyGeneratedAddress() {
127128
}
128129

129130
function generateAddress() {
130-
let backAddress = $("#connect_address").val();
131131
try {
132+
if (!addressInfoForm.checkValidity()) throw Error("invalid input")
133+
let backAddress = $("#connect_address").val();
132134
let url = new URL("https://" + backAddress);
133135
let finalAddress = "";
134136
let host = url.hostname;
@@ -155,6 +157,7 @@ function generateAddress() {
155157
function submittedListen() {
156158
let user = ($("#listen_username").val() as string).trim();
157159
if (!user) return;
160+
bootstrap.Modal.getInstance("#listenModal")!.hide()
158161
if (($("#listen_online")[0] as HTMLInputElement).checked) {
159162
sendSocket(JSON.stringify({
160163
action: "temp_code_login",
@@ -176,6 +179,7 @@ function submittedListen() {
176179
}
177180

178181
function submittedSendToken() {
182+
bootstrap.Modal.getInstance("#sendTokenModal")!.hide()
179183
let account = findAccountByMcName($("#send_token_user").val() as string)
180184
if (!account) return;
181185
account.acquireActiveToken()
@@ -802,7 +806,7 @@ function listenStoredTokens() {
802806

803807
function onWsConnect(e: Event) {
804808
let msg = "connected";
805-
let socketHost = new URL(socket!!.url).host;
809+
let socketHost = new URL(socket!.url).host;
806810
if (socketHost != location.host) msg += ` to ${socketHost}`;
807811
setWsStatus(msg);
808812
resetHtml();

0 commit comments

Comments
 (0)