Skip to content

Commit 557335d

Browse files
committed
Adds assert on logging on debug builds
1 parent 0888b28 commit 557335d

3 files changed

Lines changed: 94 additions & 8 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,4 +1081,4 @@ This project is licensed under the MIT license. See the <a href="https://github.
10811081
[license-image]: https://img.shields.io/npm/l/react-native-auth0.svg?style=flat-square
10821082
[license-url]: #license
10831083
[downloads-image]: https://img.shields.io/npm/dm/react-native-auth0.svg?style=flat-square
1084-
[downloads-url]: https://npmjs.org/package/react-native-auth0
1084+
[downloads-url]: https://npmjs.org/package/react-native-auth0

android/src/main/java/com/auth0/react/A0Auth0Module.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
9999
networkingOptions: ReadableMap?,
100100
isDebuggable: Boolean
101101
): DefaultClient =
102-
networkingOptions?.let { buildNetworkingClient(it, isDebuggable) } ?: DefaultClient()
102+
networkingOptions?.let { buildNetworkingClient(it, isDebuggable) } ?: DefaultClient.Builder().build()
103103
}
104104

105105
private val errorCodeMap = mapOf(

android/src/test/java/com/auth0/react/A0Auth0ModuleNetworkingOptionsTest.kt

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ import com.facebook.react.bridge.JavaOnlyMap
66
import okhttp3.mockwebserver.MockResponse
77
import okhttp3.mockwebserver.MockWebServer
88
import org.junit.After
9+
import org.junit.Assert.assertFalse
910
import org.junit.Assert.assertTrue
1011
import org.junit.Assert.fail
1112
import org.junit.Before
1213
import org.junit.Test
1314
import java.io.IOException
1415
import java.util.concurrent.TimeUnit
16+
import java.util.logging.Handler
17+
import java.util.logging.Level
18+
import java.util.logging.LogRecord
19+
import java.util.logging.Logger
1520
import kotlin.system.measureTimeMillis
1621

1722
// Proves that A0Auth0Module.buildNetworkingClient() genuinely threads networkingOptions
@@ -20,16 +25,66 @@ import kotlin.system.measureTimeMillis
2025
class A0Auth0ModuleNetworkingOptionsTest {
2126

2227
private lateinit var server: MockWebServer
28+
private lateinit var logCapture: LogCapture
29+
private lateinit var okHttpLogger: Logger
2330

2431
@Before
2532
fun setUp() {
2633
server = MockWebServer()
2734
server.start()
35+
36+
// Set up log capture for OkHttp's HttpLoggingInterceptor
37+
// Capture at root logger level to catch all logging regardless of hierarchy
38+
logCapture = LogCapture()
39+
okHttpLogger = Logger.getLogger("") // Root logger catches everything
40+
okHttpLogger.level = Level.ALL
41+
okHttpLogger.addHandler(logCapture)
2842
}
2943

3044
@After
3145
fun tearDown() {
3246
server.shutdown()
47+
okHttpLogger.removeHandler(logCapture)
48+
}
49+
50+
/**
51+
* Captures java.util.logging records so we can verify Auth0.Android's
52+
* HttpLoggingInterceptor is (or isn't) writing request/response bodies.
53+
*/
54+
private class LogCapture : Handler() {
55+
private val records = mutableListOf<LogRecord>()
56+
57+
override fun publish(record: LogRecord) {
58+
records.add(record)
59+
}
60+
61+
override fun flush() {}
62+
override fun close() {}
63+
64+
fun hasRequestOrResponseBodyLogs(): Boolean {
65+
return records.any { record ->
66+
val message = record.message ?: ""
67+
val loggerName = record.loggerName ?: ""
68+
69+
(loggerName.contains("okhttp", ignoreCase = true) ||
70+
loggerName.contains("http", ignoreCase = true)) &&
71+
(message.contains("--> ") || // Request line: "--> GET /token"
72+
message.contains("<-- ") || // Response line: "<-- 200 OK"
73+
message.contains("Content-") || // Headers like Content-Type, Content-Length
74+
message.contains("access_token") || // Response body content
75+
message.contains("{\"")) // JSON body start
76+
}
77+
}
78+
79+
fun getAllLogMessages(): String {
80+
return records.joinToString("\n") { record ->
81+
"[${record.loggerName}] ${record.message}"
82+
}
83+
}
84+
85+
fun clear() {
86+
records.clear()
87+
}
3388
}
3489

3590
@Test
@@ -78,21 +133,52 @@ class A0Auth0ModuleNetworkingOptionsTest {
78133

79134
@Test
80135
fun `enableLogging is ignored on a non-debuggable build even when requested`() {
81-
server.enqueue(MockResponse().setBody("{}"))
136+
server.enqueue(MockResponse().setBody("""{"access_token": "secret123"}"""))
137+
138+
logCapture.clear()
82139

83140
// SECURITY: If the isDebuggable gate is ever removed, Auth0.Android attaches its
84141
// logging interceptor which logs full request/response bodies (including tokens).
85-
// This test would crash ("Method android.util.Log not mocked") if that happens,
86-
// catching the security regression before it ships.
142+
// Auth0.Android's interceptor writes through java.util.logging, not android.util.Log,
143+
// so we must assert on captured log records rather than relying on a crash.
87144
val client = A0Auth0Module.buildNetworkingClient(
88145
JavaOnlyMap.of("enableLogging", true),
89146
isDebuggable = false
90147
)
91148

92-
client.load(server.url("/").toString(), RequestOptions(HttpMethod.GET))
149+
client.load(server.url("/token").toString(), RequestOptions(HttpMethod.GET))
93150

94-
val recordedRequest = server.takeRequest()
95-
assertTrue(recordedRequest.method == "GET")
151+
// Verify that NO request/response body logs were written. If the isDebuggable gate
152+
// is removed, this assertion will fail because the interceptor will log the response
153+
// body containing "access_token": "secret123".
154+
assertFalse(
155+
"Expected no HTTP body logs on non-debuggable build, but logging was enabled",
156+
logCapture.hasRequestOrResponseBodyLogs()
157+
)
158+
}
159+
160+
@Test
161+
fun `enableLogging actually logs request and response bodies on debuggable builds`() {
162+
server.enqueue(MockResponse().setBody("""{"access_token": "secret456"}"""))
163+
164+
logCapture.clear()
165+
166+
// Positive test: verify that enableLogging actually works when isDebuggable = true.
167+
val client = A0Auth0Module.buildNetworkingClient(
168+
JavaOnlyMap.of("enableLogging", true),
169+
isDebuggable = true
170+
)
171+
172+
client.load(server.url("/token").toString(), RequestOptions(HttpMethod.GET))
173+
174+
// The interceptor should have logged the request and response, proving that:
175+
// (a) our test harness correctly captures logs, and
176+
// (b) the enableLogging option genuinely enables logging when allowed.
177+
val allLogs = logCapture.getAllLogMessages()
178+
assertTrue(
179+
"Expected HTTP body logs on debuggable build with enableLogging=true. Captured logs:\n$allLogs",
180+
logCapture.hasRequestOrResponseBodyLogs()
181+
)
96182
}
97183

98184
@Test

0 commit comments

Comments
 (0)