[Android] Remote access: extract authentication routing
Nicolas Pomepuy
git at videolan.org
Thu Sep 10 04:41:21 UTC 2026
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Tue Sep 1 10:32:44 2026 +0200| [8cd6427bf0b79a52218ccc077b356019ca84f134] | committer: Nicolas Pomepuy
Remote access: extract authentication routing
> https://code.videolan.org/videolan/vlc-android/commit/8cd6427bf0b79a52218ccc077b356019ca84f134
---
.../vlc/remoteaccessserver/RemoteAccessRouting.kt | 56 +----------
.../routing/RemoteAccessRoutingAuth.kt | 104 +++++++++++++++++++++
2 files changed, 109 insertions(+), 51 deletions(-)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
index 1ffa6436bd..814b8dabf3 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
@@ -166,52 +166,9 @@ private const val TAG = "VLC/HttpSharingServer"
fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
val settings = Settings.getInstance(appContext)
staticFiles("", File(getServerFiles(appContext)))
- //the client is requesting a new code.
- // if the formparameters "challenge" is sent. Remove the corresponding code
- post("/code") {
- val formParameters = try {
- call.receiveParameters()
- } catch (e: Exception) {
- null
- }
- val challenge = if (formParameters == null) null else formParameters["challenge"].toString()
- if (!challenge.isNullOrBlank()) {
- RemoteAccessOTP.removeCodeWithChallenge(challenge)
- }
- val code = RemoteAccessOTP.getFirstValidCode(appContext)
- scope.launch {
- RemoteAccessUtils.otpFlow.emit(code.code)
- }
- call.respondText(code.challenge)
- }
- //Verify the code and inject the cookie if valid
- post("/verify-code") {
- val formParameters = try {
- call.receiveParameters()
- } catch (e: Exception) {
- null
- }
- val idString = formParameters?.get("code")
- if (idString == null){
- call.respond(HttpStatusCode.BadRequest)
- return at post
- }
- if (RemoteAccessOTP.verifyCode(appContext, idString)) {
- //verification is OK
- RemoteAccessSession.injectCookie(call, settings)
- scope.launch {
- RemoteAccessUtils.otpFlow.emit(null)
- }
- call.respondRedirect("/")
- return at post
- }
- if (isFlooding(appContext, call.request.origin.remoteAddress)) {
- Log.w(TAG, "Too many requests from ${call.request.origin.remoteAddress}")
- call.respond(HttpStatusCode.TooManyRequests)
- return at post
- }
- call.respondRedirect("/index.html#/login/error")
- }
+
+ publicAuthRouting(appContext, scope, settings)
+
// Main end point redirect to index.html
get("/") {
call.respondRedirect("index.html", permanent = true)
@@ -443,12 +400,14 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
}
authenticate("user_session", optional = RemoteAccessServer.byPassAuth) {
+ authenticatedAuthRouting()
post("/logs") {
val formParameters = try {
call.receiveParameters()
} catch (e: Exception) {
null
}
+
val logs = buildString {
formParameters?.forEach { s, strings ->
if (s.contains("[time]"))
@@ -501,11 +460,6 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
else
call.respondText("")
}
- //Provide a Websocket auth ticket as auth is validated
- get("/wsticket") {
- val ticket = RemoteAccessWebSockets.createTicket()
- call.respondText(ticket)
- }
// List of all the videos
get("/video-list") {
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt
new file mode 100644
index 0000000000..ef473cf655
--- /dev/null
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/routing/RemoteAccessRoutingAuth.kt
@@ -0,0 +1,104 @@
+/*
+ * ************************************************************************
+ * RemoteAccessRoutingAuth.kt
+ * *************************************************************************
+ * Copyright © 2026 VLC authors and VideoLAN
+ * Author: Nicolas POMEPUY
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ * **************************************************************************
+ *
+ *
+ */
+
+package org.videolan.vlc.remoteaccessserver.routing
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.Log
+import io.ktor.http.HttpStatusCode
+import io.ktor.server.application.call
+import io.ktor.server.plugins.origin
+import io.ktor.server.request.receiveParameters
+import io.ktor.server.response.respond
+import io.ktor.server.response.respondRedirect
+import io.ktor.server.response.respondText
+import io.ktor.server.routing.Route
+import io.ktor.server.routing.get
+import io.ktor.server.routing.post
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.videolan.vlc.remoteaccessserver.RemoteAccessOTP
+import org.videolan.vlc.remoteaccessserver.RemoteAccessSession
+import org.videolan.vlc.remoteaccessserver.websockets.RemoteAccessWebSockets
+import org.videolan.vlc.util.RemoteAccessUtils
+
+private const val TAG = "RARoutingAuth"
+
+fun Route.publicAuthRouting(appContext: Context, scope: CoroutineScope, settings: SharedPreferences) {
+ //the client is requesting a new code.
+ // if the formparameters "challenge" is sent. Remove the corresponding code
+ post("/code") {
+ val formParameters = try {
+ call.receiveParameters()
+ } catch (_: Exception) {
+ null
+ }
+ val challenge = formParameters?.get("challenge")
+ if (!challenge.isNullOrBlank()) {
+ RemoteAccessOTP.removeCodeWithChallenge(challenge)
+ }
+ val code = RemoteAccessOTP.getFirstValidCode(appContext)
+ scope.launch {
+ RemoteAccessUtils.otpFlow.emit(code.code)
+ }
+ call.respondText(code.challenge)
+ }
+ //Verify the code and inject the cookie if valid
+ post("/verify-code") {
+ val formParameters = try {
+ call.receiveParameters()
+ } catch (e: Exception) {
+ null
+ }
+ val idString = formParameters?.get("code")
+ if (idString == null){
+ call.respond(HttpStatusCode.BadRequest)
+ return at post
+ }
+ if (RemoteAccessOTP.verifyCode(appContext, idString)) {
+ //verification is OK
+ RemoteAccessSession.injectCookie(call, settings)
+ scope.launch {
+ RemoteAccessUtils.otpFlow.emit(null)
+ }
+ call.respondRedirect("/")
+ return at post
+ }
+ if (isFlooding(appContext, call.request.origin.remoteAddress)) {
+ Log.w(TAG, "Too many requests from ${call.request.origin.remoteAddress}")
+ call.respond(HttpStatusCode.TooManyRequests)
+ return at post
+ }
+ call.respondRedirect("/index.html#/login/error")
+ }
+}
+
+fun Route.authenticatedAuthRouting() {
+ //Provide a Websocket auth ticket as auth is validated
+ get("/wsticket") {
+ val ticket = RemoteAccessWebSockets.createTicket()
+ call.respondText(ticket)
+ }
+}
More information about the Android
mailing list