diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/StringMasking.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/StringMasking.kt new file mode 100644 index 000000000..c45ed3338 --- /dev/null +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/StringMasking.kt @@ -0,0 +1,26 @@ +package io.github.jan.supabase + +import io.ktor.http.Headers +import io.ktor.http.Url +import io.ktor.util.toMap + +internal fun maskString(value: String, visibleCharacters: Int = 2, showLength: Boolean = false): String { + if(value.isBlank()) return value; + return value.take(visibleCharacters) + "..." + if(showLength) " (len=${value.length})" else "" +} + +internal fun maskUrl(value: Url, visibleCharacters: Int = 2): String { + return buildUrl(value) { + host = "${host.take(visibleCharacters)}..." + } +} + +private val SENSITIVE_HEADERS = listOf("apikey", "Authorization") + +internal fun maskHeaders(headers: Headers): String = headers.toMap().mapValues { (key, value) -> + if(key in SENSITIVE_HEADERS) { + value.firstOrNull()?.let { + listOf(if(key == "Authorization") "Bearer ${maskString(it.drop(7), showLength = true)}" else maskString(it, showLength = true)) + } + } else value +}.toString() diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/UrlUtils.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/UrlUtils.kt new file mode 100644 index 000000000..aabb86eea --- /dev/null +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/UrlUtils.kt @@ -0,0 +1,19 @@ +package io.github.jan.supabase + +import io.github.jan.supabase.annotations.SupabaseInternal +import io.ktor.http.URLBuilder +import io.ktor.http.Url + +@SupabaseInternal +inline fun buildUrl(baseUrl: String, init: URLBuilder.() -> Unit): String { + val builder = URLBuilder(baseUrl) + builder.init() + return builder.buildString() +} + +@SupabaseInternal +inline fun buildUrl(baseUrl: Url, init: URLBuilder.() -> Unit): String { + val builder = URLBuilder(baseUrl) + builder.init() + return builder.buildString() +} \ No newline at end of file diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/Utils.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/Utils.kt index 2637bda85..beccf503a 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/Utils.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/Utils.kt @@ -5,7 +5,6 @@ import io.github.jan.supabase.exceptions.SupabaseEncodingException import io.github.jan.supabase.logging.i import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import io.ktor.http.URLBuilder import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.MissingFieldException import kotlinx.serialization.SerializationException @@ -32,13 +31,6 @@ suspend inline fun HttpResponse.safeBody(context: String? = null): T } } -@SupabaseInternal -inline fun buildUrl(baseUrl: String, init: URLBuilder.() -> Unit): String { - val builder = URLBuilder(baseUrl) - builder.init() - return builder.buildString() -} - @SupabaseInternal fun String.toJsonObject(): JsonObject = supabaseJson.decodeFromString(this) diff --git a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt index bbbaf69a4..5194bf7a7 100644 --- a/Supabase/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt +++ b/Supabase/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt @@ -1,5 +1,7 @@ package io.github.jan.supabase.exceptions +import io.github.jan.supabase.maskHeaders +import io.github.jan.supabase.maskUrl import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.request @@ -17,8 +19,8 @@ import io.ktor.client.statement.request */ open class RestException(val error: String, val description: String?, val response: HttpResponse): Exception(""" $error${description?.let { "\n$it" } ?: ""} - URL: ${response.request.url} - Headers: ${response.request.headers.entries()} + URL: ${maskUrl(response.request.url)} + Headers: ${maskHeaders(response.request.headers)} Http Method: ${response.request.method.value} """.trimIndent()) { diff --git a/Supabase/src/commonTest/kotlin/StringMaskingTest.kt b/Supabase/src/commonTest/kotlin/StringMaskingTest.kt new file mode 100644 index 000000000..5826ca389 --- /dev/null +++ b/Supabase/src/commonTest/kotlin/StringMaskingTest.kt @@ -0,0 +1,46 @@ +import io.github.jan.supabase.maskHeaders +import io.github.jan.supabase.maskString +import io.github.jan.supabase.maskUrl +import io.ktor.http.Url +import io.ktor.http.headers +import kotlin.test.Test +import kotlin.test.assertEquals + +class StringMaskingTest { + + @Test + fun testEmptyString() { + assertEquals("", maskString("")) + } + + @Test + fun testString() { + assertEquals("ab...", maskString("abcdefghi")) + } + + @Test + fun testStringWithLength() { + assertEquals("ab... (len=5)", maskString("abcde", showLength = true)) + } + + @Test + fun testUrl() { + assertEquals("https://ab.../test?parameter=true", maskUrl(Url("https://abcdefg.supabase.co/test?parameter=true"))) + } + + @Test + fun testHeaderMasking() { + val headers = headers { + set("aa", "bb") + set("another", "one") + set("apikey", "areallylongkey") + set("Authorization", "Bearer thisisasecretkey") + } + val maskedString = maskHeaders(headers) + assertEquals( + "{aa=[bb], another=[one], apikey=[ar... (len=14)], Authorization=[Bearer th... (len=16)]}", + maskedString + ) + } + +} \ No newline at end of file