From fd77d2a614d93a15c6edc176b5efc6e0b1267382 Mon Sep 17 00:00:00 2001 From: Tomeshwari-02 <179694969+Tomeshwari-02@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:00:01 +0530 Subject: [PATCH 1/2] Fix thread-safe auth token storage --- .../network/interceptors/AuthTokenManager.kt | 55 ++++++++++++++++ .../TokenESanjeevaniInterceptor.kt | 9 ++- .../TokenInsertAbhaInterceptor.kt | 17 +++-- .../interceptors/TokenInsertTmcInterceptor.kt | 16 ++--- .../interceptors/AuthTokenManagerTest.kt | 65 +++++++++++++++++++ 5 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt create mode 100644 app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt new file mode 100644 index 000000000..9c6ae833d --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt @@ -0,0 +1,55 @@ +package org.piramalswasthya.cho.network.interceptors + +import java.util.concurrent.atomic.AtomicReference + +/** + * Thread-safe token holder for the network interceptors. + * + * The app updates these values from multiple workers and screens, so the + * storage needs to be safe for concurrent reads and writes. + */ +object AuthTokenManager { + private val tmcToken = AtomicReference("") + private val tmcJwt = AtomicReference("") + private val abhaToken = AtomicReference("") + private val abhaXToken = AtomicReference("") + private val eSanjeevaniToken = AtomicReference("") + + fun setTmcToken(token: String) { + tmcToken.set(token) + } + + fun getTmcToken(): String = tmcToken.get() + + fun setTmcJwt(jwt: String) { + tmcJwt.set(jwt) + } + + fun getTmcJwt(): String = tmcJwt.get() + + fun setAbhaToken(token: String?) { + abhaToken.set(token.orEmpty()) + } + + fun getAbhaToken(): String = abhaToken.get() + + fun setAbhaXToken(token: String?) { + abhaXToken.set(token.orEmpty()) + } + + fun getAbhaXToken(): String = abhaXToken.get() + + fun setESanjeevaniToken(token: String) { + eSanjeevaniToken.set(token) + } + + fun getESanjeevaniToken(): String = eSanjeevaniToken.get() + + fun clear() { + tmcToken.set("") + tmcJwt.set("") + abhaToken.set("") + abhaXToken.set("") + eSanjeevaniToken.set("") + } +} diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt index c90b3a41b..c4b95d3a9 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt @@ -7,13 +7,12 @@ import timber.log.Timber class TokenESanjeevaniInterceptor : Interceptor{ companion object { - private var TOKEN: String = "" fun setToken(iToken: String) { - TOKEN = iToken + AuthTokenManager.setESanjeevaniToken(iToken) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getESanjeevaniToken() } } @@ -22,10 +21,10 @@ class TokenESanjeevaniInterceptor : Interceptor{ if (request.header("No-Auth") == null) { request = request .newBuilder() - .addHeader("Authorization", "Bearer $TOKEN") + .addHeader("Authorization", "Bearer ${AuthTokenManager.getESanjeevaniToken()}") .build() } Timber.d("Request : $request") return chain.proceed(request) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt index 8f34e8367..0abef4864 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt @@ -7,22 +7,20 @@ import timber.log.Timber class TokenInsertAbhaInterceptor : Interceptor { companion object { - private var TOKEN: String = "" - private var XToken: String = "" fun setToken(iToken: String?) { - TOKEN = iToken ?: "" + AuthTokenManager.setAbhaToken(iToken) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getAbhaToken() } fun setXToken(xToken: String?) { - XToken = xToken ?: "" + AuthTokenManager.setAbhaXToken(xToken) } fun getXToken(): String { - return XToken + return AuthTokenManager.getAbhaXToken() } } @@ -33,18 +31,19 @@ class TokenInsertAbhaInterceptor : Interceptor { .newBuilder() .addHeader( "Authorization", - "Bearer $TOKEN" + "Bearer ${AuthTokenManager.getAbhaToken()}" ) .build() } val url = request.url.toString() if (url.contains("getCard") || url.contains("getPngCard") || url.contains("abha-card")) { - if (XToken.isNotEmpty()) { + val xToken = AuthTokenManager.getAbhaXToken() + if (xToken.isNotEmpty()) { request = request .newBuilder() .addHeader( "x-token", - "Bearer $XToken" + "Bearer $xToken" ) .build() } else { diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt index 7e0d60182..63d8d591b 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt @@ -8,22 +8,20 @@ class TokenInsertTmcInterceptor : Interceptor{ companion object { - private var TOKEN: String = "" fun setToken(iToken: String) { - TOKEN = iToken + AuthTokenManager.setTmcToken(iToken) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getTmcToken() } - private var JWT: String = "" fun setJwt(iJWT: String) { - JWT = iJWT + AuthTokenManager.setTmcJwt(iJWT) } fun getJwt(): String { - return JWT + return AuthTokenManager.getTmcJwt() } } @@ -33,11 +31,11 @@ class TokenInsertTmcInterceptor : Interceptor{ if (request.header("No-Auth") == null) { request = request .newBuilder() - .addHeader("Authorization", TOKEN) - .addHeader("Jwttoken" , JWT) + .addHeader("Authorization", AuthTokenManager.getTmcToken()) + .addHeader("Jwttoken" , AuthTokenManager.getTmcJwt()) .build() } Timber.d("Request : $request") return chain.proceed(request) } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt new file mode 100644 index 000000000..58f058c98 --- /dev/null +++ b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt @@ -0,0 +1,65 @@ +package org.piramalswasthya.cho.network.interceptors + +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class AuthTokenManagerTest { + + @Before + fun setUp() { + AuthTokenManager.clear() + } + + @After + fun tearDown() { + AuthTokenManager.clear() + } + + @Test + fun storesValuesIndependentlyForEachInterceptor() { + AuthTokenManager.setTmcToken("tmc-token") + AuthTokenManager.setTmcJwt("tmc-jwt") + AuthTokenManager.setAbhaToken("abha-token") + AuthTokenManager.setAbhaXToken("abha-x-token") + AuthTokenManager.setESanjeevaniToken("esanjeevani-token") + + assertEquals("tmc-token", AuthTokenManager.getTmcToken()) + assertEquals("tmc-jwt", AuthTokenManager.getTmcJwt()) + assertEquals("abha-token", AuthTokenManager.getAbhaToken()) + assertEquals("abha-x-token", AuthTokenManager.getAbhaXToken()) + assertEquals("esanjeevani-token", AuthTokenManager.getESanjeevaniToken()) + } + + @Test + fun concurrentWritesRemainReadable() { + val executor = Executors.newFixedThreadPool(4) + val ready = CountDownLatch(1) + val tasks = (0 until 32).map { index -> + executor.submit { + ready.await(5, TimeUnit.SECONDS) + AuthTokenManager.setTmcToken("tmc-$index") + AuthTokenManager.setTmcJwt("jwt-$index") + AuthTokenManager.setAbhaToken("abha-$index") + AuthTokenManager.setAbhaXToken("x-$index") + AuthTokenManager.setESanjeevaniToken("es-$index") + } + } + + ready.countDown() + tasks.forEach { it.get(5, TimeUnit.SECONDS) } + executor.shutdown() + executor.awaitTermination(5, TimeUnit.SECONDS) + + assertTrue(AuthTokenManager.getTmcToken().startsWith("tmc-")) + assertTrue(AuthTokenManager.getTmcJwt().startsWith("jwt-")) + assertTrue(AuthTokenManager.getAbhaToken().startsWith("abha-")) + assertTrue(AuthTokenManager.getAbhaXToken().startsWith("x-")) + assertTrue(AuthTokenManager.getESanjeevaniToken().startsWith("es-")) + } +} From 0e7ef8025783ce016ea362cdd9c62b01f9e5d1fb Mon Sep 17 00:00:00 2001 From: Tomeshwari-02 <179694969+Tomeshwari-02@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:15:06 +0530 Subject: [PATCH 2/2] Make TMC credentials atomic --- .../network/interceptors/AuthTokenManager.kt | 24 +++++----- .../interceptors/TokenInsertTmcInterceptor.kt | 19 +++++--- .../interceptors/AuthTokenManagerTest.kt | 46 +++++++++++-------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt index 9c6ae833d..f3340cd87 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt @@ -9,23 +9,22 @@ import java.util.concurrent.atomic.AtomicReference * storage needs to be safe for concurrent reads and writes. */ object AuthTokenManager { - private val tmcToken = AtomicReference("") - private val tmcJwt = AtomicReference("") + + data class TmcCredentials( + val token: String = "", + val jwt: String = "" + ) + + private val tmcCredentials = AtomicReference(TmcCredentials()) private val abhaToken = AtomicReference("") private val abhaXToken = AtomicReference("") private val eSanjeevaniToken = AtomicReference("") - fun setTmcToken(token: String) { - tmcToken.set(token) - } - - fun getTmcToken(): String = tmcToken.get() - - fun setTmcJwt(jwt: String) { - tmcJwt.set(jwt) + fun setTmcCredentials(token: String, jwt: String) { + tmcCredentials.set(TmcCredentials(token, jwt)) } - fun getTmcJwt(): String = tmcJwt.get() + fun getTmcCredentials(): TmcCredentials = tmcCredentials.get() fun setAbhaToken(token: String?) { abhaToken.set(token.orEmpty()) @@ -46,8 +45,7 @@ object AuthTokenManager { fun getESanjeevaniToken(): String = eSanjeevaniToken.get() fun clear() { - tmcToken.set("") - tmcJwt.set("") + tmcCredentials.set(TmcCredentials()) abhaToken.set("") abhaXToken.set("") eSanjeevaniToken.set("") diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt index 63d8d591b..c8f3c8928 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt @@ -8,20 +8,26 @@ class TokenInsertTmcInterceptor : Interceptor{ companion object { + fun setCredentials(token: String, jwt: String) { + AuthTokenManager.setTmcCredentials(token, jwt) + } + fun setToken(iToken: String) { - AuthTokenManager.setTmcToken(iToken) + val credentials = AuthTokenManager.getTmcCredentials() + AuthTokenManager.setTmcCredentials(iToken, credentials.jwt) } fun getToken(): String { - return AuthTokenManager.getTmcToken() + return AuthTokenManager.getTmcCredentials().token } fun setJwt(iJWT: String) { - AuthTokenManager.setTmcJwt(iJWT) + val credentials = AuthTokenManager.getTmcCredentials() + AuthTokenManager.setTmcCredentials(credentials.token, iJWT) } fun getJwt(): String { - return AuthTokenManager.getTmcJwt() + return AuthTokenManager.getTmcCredentials().jwt } } @@ -29,10 +35,11 @@ class TokenInsertTmcInterceptor : Interceptor{ override fun intercept(chain: Interceptor.Chain): Response { var request = chain.request() if (request.header("No-Auth") == null) { + val tmcCredentials = AuthTokenManager.getTmcCredentials() request = request .newBuilder() - .addHeader("Authorization", AuthTokenManager.getTmcToken()) - .addHeader("Jwttoken" , AuthTokenManager.getTmcJwt()) + .addHeader("Authorization", tmcCredentials.token) + .addHeader("Jwttoken" , tmcCredentials.jwt) .build() } Timber.d("Request : $request") diff --git a/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt index 58f058c98..633747dd8 100644 --- a/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt +++ b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt @@ -23,14 +23,13 @@ class AuthTokenManagerTest { @Test fun storesValuesIndependentlyForEachInterceptor() { - AuthTokenManager.setTmcToken("tmc-token") - AuthTokenManager.setTmcJwt("tmc-jwt") + AuthTokenManager.setTmcCredentials("tmc-token", "tmc-jwt") AuthTokenManager.setAbhaToken("abha-token") AuthTokenManager.setAbhaXToken("abha-x-token") AuthTokenManager.setESanjeevaniToken("esanjeevani-token") - assertEquals("tmc-token", AuthTokenManager.getTmcToken()) - assertEquals("tmc-jwt", AuthTokenManager.getTmcJwt()) + assertEquals("tmc-token", AuthTokenManager.getTmcCredentials().token) + assertEquals("tmc-jwt", AuthTokenManager.getTmcCredentials().jwt) assertEquals("abha-token", AuthTokenManager.getAbhaToken()) assertEquals("abha-x-token", AuthTokenManager.getAbhaXToken()) assertEquals("esanjeevani-token", AuthTokenManager.getESanjeevaniToken()) @@ -39,25 +38,32 @@ class AuthTokenManagerTest { @Test fun concurrentWritesRemainReadable() { val executor = Executors.newFixedThreadPool(4) - val ready = CountDownLatch(1) - val tasks = (0 until 32).map { index -> - executor.submit { - ready.await(5, TimeUnit.SECONDS) - AuthTokenManager.setTmcToken("tmc-$index") - AuthTokenManager.setTmcJwt("jwt-$index") - AuthTokenManager.setAbhaToken("abha-$index") - AuthTokenManager.setAbhaXToken("x-$index") - AuthTokenManager.setESanjeevaniToken("es-$index") + try { + val ready = CountDownLatch(1) + val tasks = (0 until 32).map { index -> + executor.submit { + check(ready.await(5, TimeUnit.SECONDS)) + AuthTokenManager.setTmcCredentials("tmc-$index", "jwt-$index") + AuthTokenManager.setAbhaToken("abha-$index") + AuthTokenManager.setAbhaXToken("x-$index") + AuthTokenManager.setESanjeevaniToken("es-$index") + } } - } - ready.countDown() - tasks.forEach { it.get(5, TimeUnit.SECONDS) } - executor.shutdown() - executor.awaitTermination(5, TimeUnit.SECONDS) + ready.countDown() + tasks.forEach { it.get(5, TimeUnit.SECONDS) } + } finally { + executor.shutdownNow() + executor.awaitTermination(5, TimeUnit.SECONDS) + } - assertTrue(AuthTokenManager.getTmcToken().startsWith("tmc-")) - assertTrue(AuthTokenManager.getTmcJwt().startsWith("jwt-")) + val tmcCredentials = AuthTokenManager.getTmcCredentials() + assertTrue(tmcCredentials.token.startsWith("tmc-")) + assertTrue(tmcCredentials.jwt.startsWith("jwt-")) + assertEquals( + tmcCredentials.token.substringAfter('-'), + tmcCredentials.jwt.substringAfter('-') + ) assertTrue(AuthTokenManager.getAbhaToken().startsWith("abha-")) assertTrue(AuthTokenManager.getAbhaXToken().startsWith("x-")) assertTrue(AuthTokenManager.getESanjeevaniToken().startsWith("es-"))