diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 569127a2..e3baf329 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -66,7 +66,11 @@ dependencies { implementation(libs.androidx.work.runtime.ktx) implementation(libs.coil) implementation(libs.copydown) + implementation(libs.jsoup) implementation(libs.kotlinx.collections.immutable) implementation(libs.kotlinx.coroutines.core) implementation(libs.sqldelight.extensions.coroutines) + testImplementation(libs.kotest.assertions.core) + testImplementation(libs.kotest.runner.junit5) + testImplementation(libs.okhttp.mockwebserver) } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepository.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepository.kt new file mode 100644 index 00000000..147bc11a --- /dev/null +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright © 2023 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.viewmodel + +import dev.msfjarvis.claw.android.injection.IODispatcher +import dev.msfjarvis.claw.api.injection.BaseUrl +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.Jsoup + +/** Helper for extracting CSRF token for authenticated requests to https://lobste.rs. */ +class CSRFRepository +@Inject +constructor( + private val okHttpClient: OkHttpClient, + @IODispatcher private val dispatcher: CoroutineDispatcher, + @BaseUrl private val url: String, +) { + suspend fun extractToken(): String? { + val request = Request.Builder().url(url).get().build() + return withContext(dispatcher) { + okHttpClient.newCall(request).execute().use { response -> + val doc = Jsoup.parse(response.body?.string() ?: return@use null) + val element = doc.select("meta[name=\"csrf-token\"]").first() ?: return@use null + return@use element.attr("content") + } + } + } +} diff --git a/android/src/test/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepositoryTest.kt b/android/src/test/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepositoryTest.kt new file mode 100644 index 00000000..ca9428ff --- /dev/null +++ b/android/src/test/kotlin/dev/msfjarvis/claw/android/viewmodel/CSRFRepositoryTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright © 2023 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.viewmodel + +import io.kotest.core.spec.Spec +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.Dispatchers +import okhttp3.OkHttpClient +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest + +class CSRFRepositoryTest : FunSpec() { + private val server = MockWebServer() + + init { + test("Correctly extracts CSRF token").config(coroutineTestScope = true) { + val repo = + CSRFRepository( + OkHttpClient.Builder().build(), + Dispatchers.Default, + server.url("/").toString(), + ) + repo.extractToken() shouldBe + "OZWykgFemPVeOSNmB53-ccKXe458X7xCInO1-qzFU6nk_9RCSrSQqS9OPmA5_pyy8qD3IYAIZ7XfAM3gdhJpkQ" + } + } + + override suspend fun beforeSpec(spec: Spec) { + super.beforeSpec(spec) + val dispatcher = + object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + return when (val path = request.path) { + "/" -> + MockResponse() + .setResponseCode(200) + .setBody( + javaClass.classLoader!! + .getResourceAsStream("csrf_page.html") + .readAllBytes() + .decodeToString(), + ) + else -> error("Invalid path: $path") + } + } + } + server.dispatcher = dispatcher + } +} diff --git a/android/src/test/resources/csrf_page.html b/android/src/test/resources/csrf_page.html new file mode 100644 index 00000000..f27c3a17 --- /dev/null +++ b/android/src/test/resources/csrf_page.html @@ -0,0 +1,5 @@ +