feat: handle cloudflare captcha in under attack mode
This commit is contained in:
parent
6f48f65bd1
commit
0adb40f661
|
|
@ -83,6 +83,7 @@ android {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
compose true
|
compose true
|
||||||
}
|
}
|
||||||
composeOptions {
|
composeOptions {
|
||||||
|
|
@ -163,6 +164,9 @@ dependencies {
|
||||||
implementation "androidx.datastore:datastore-preferences:1.1.0-alpha01"
|
implementation "androidx.datastore:datastore-preferences:1.1.0-alpha01"
|
||||||
implementation "androidx.datastore:datastore:1.1.0-alpha01"
|
implementation "androidx.datastore:datastore:1.1.0-alpha01"
|
||||||
|
|
||||||
|
// Libraries used for legacy View-based UI
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha09"
|
||||||
|
|
||||||
// JDK Desugaring - polyfill for new Java APIs
|
// JDK Desugaring - polyfill for new Java APIs
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.sentry.auto-init"
|
android:name="io.sentry.auto-init"
|
||||||
android:value="false" />
|
android:value="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.Revolt">
|
android:theme="@style/Theme.Revolt">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
@ -30,6 +30,9 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activities.WebChallengeActivity"
|
||||||
|
android:theme="@style/Theme.Revolt" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package chat.revolt.activities
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import chat.revolt.api.REVOLT_BASE
|
||||||
|
import chat.revolt.api.buildUserAgent
|
||||||
|
import chat.revolt.databinding.ActivityWebchallengeBinding
|
||||||
|
|
||||||
|
private class WebChallengeClient(val pageLoaded: () -> Unit) : WebViewClient() {
|
||||||
|
@Override
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
pageLoaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebChallengeActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityWebchallengeBinding
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityWebchallengeBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
binding.webView.settings.apply {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
domStorageEnabled = true
|
||||||
|
userAgentString = buildUserAgent("WebChallenge")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.webView.webViewClient = WebChallengeClient {
|
||||||
|
binding.webView.evaluateJavascript(
|
||||||
|
"(function() { return document.getElementById('cf-wrapper') != null; })();"
|
||||||
|
) { result ->
|
||||||
|
if (result == "false") { // No challenge
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.webView.loadUrl(REVOLT_BASE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,10 @@ fun asJanuaryProxyUrl(url: String): String {
|
||||||
return "$REVOLT_JANUARY/proxy?url=${url}"
|
return "$REVOLT_JANUARY/proxy?url=${url}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildUserAgent(accessMethod: String = "Ktor"): String {
|
||||||
|
return "$accessMethod RevoltAndroid/${BuildConfig.VERSION_NAME} (Android ${android.os.Build.VERSION.SDK_INT}; ${android.os.Build.MANUFACTURER} ${android.os.Build.DEVICE})"
|
||||||
|
}
|
||||||
|
|
||||||
private const val BACKEND_IS_STABLE = false
|
private const val BACKEND_IS_STABLE = false
|
||||||
|
|
||||||
val RevoltJson = Json { ignoreUnknownKeys = true }
|
val RevoltJson = Json { ignoreUnknownKeys = true }
|
||||||
|
|
@ -70,10 +74,7 @@ val RevoltHttp = HttpClient(OkHttp) {
|
||||||
|
|
||||||
defaultRequest {
|
defaultRequest {
|
||||||
url(REVOLT_BASE)
|
url(REVOLT_BASE)
|
||||||
header(
|
header("User-Agent", buildUserAgent())
|
||||||
"User-Agent",
|
|
||||||
"Ktor RevoltAndroid/${BuildConfig.VERSION_NAME} (Android ${android.os.Build.VERSION.SDK_INT}; ${android.os.Build.MANUFACTURER} ${android.os.Build.DEVICE})"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package chat.revolt.api.internals
|
||||||
|
|
||||||
|
import chat.revolt.api.REVOLT_BASE
|
||||||
|
import chat.revolt.api.RevoltHttp
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
|
||||||
|
object WebChallenge {
|
||||||
|
suspend fun needsCloudflare(): Boolean {
|
||||||
|
RevoltHttp.get(REVOLT_BASE).let {
|
||||||
|
val text = it.bodyAsText()
|
||||||
|
return text.contains("window._cf_chl_opt") // FIXME Naive, prone to captcha page changing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,10 @@ package chat.revolt.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -11,6 +13,7 @@ import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
|
@ -18,7 +21,9 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
import chat.revolt.activities.WebChallengeActivity
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.internals.WebChallenge
|
||||||
import chat.revolt.api.settings.GlobalState
|
import chat.revolt.api.settings.GlobalState
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
import chat.revolt.components.screens.splash.DisconnectedScreen
|
import chat.revolt.components.screens.splash.DisconnectedScreen
|
||||||
|
|
@ -70,11 +75,19 @@ class SplashScreenViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkLoggedInState() {
|
fun checkLoggedInState() {
|
||||||
|
Log.d("SplashScreenViewModel", "Checking logged in state")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
setIsConnected(hasInternetConnection())
|
setIsConnected(hasInternetConnection())
|
||||||
|
|
||||||
if (!isConnected) return@launch
|
if (!isConnected) return@launch
|
||||||
|
|
||||||
|
val needsCloudflare = WebChallenge.needsCloudflare()
|
||||||
|
|
||||||
|
if (needsCloudflare) {
|
||||||
|
setNavigateTo("webchallenge")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
val token = kvStorage.get("sessionToken") ?: return@launch setNavigateTo("login")
|
val token = kvStorage.get("sessionToken") ?: return@launch setNavigateTo("login")
|
||||||
|
|
||||||
val valid = RevoltAPI.checkSessionToken(token)
|
val valid = RevoltAPI.checkSessionToken(token)
|
||||||
|
|
@ -107,6 +120,8 @@ fun SplashScreen(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: SplashScreenViewModel = hiltViewModel()
|
viewModel: SplashScreenViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
if (!viewModel.isConnected) {
|
if (!viewModel.isConnected) {
|
||||||
DisconnectedScreen(
|
DisconnectedScreen(
|
||||||
onRetry = {
|
onRetry = {
|
||||||
|
|
@ -143,6 +158,17 @@ fun SplashScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"webchallenge" -> {
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
WebChallengeActivity::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
viewModel.checkLoggedInState()
|
||||||
|
}
|
||||||
|
|
||||||
"home" -> {
|
"home" -> {
|
||||||
navController.navigate("chat") {
|
navController.navigate("chat") {
|
||||||
popUpTo("splash") {
|
popUpTo("splash") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/webView" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
Loading…
Reference in New Issue