From e9da018dea202677358e1142e315338104597e3b Mon Sep 17 00:00:00 2001 From: gemcoder21 <104884878+gemcoder21@users.noreply.github.com> Date: Mon, 25 May 2026 22:05:24 +0000 Subject: [PATCH] Update Android device authentication --- .../asset/DeviceAssetsSyncService.kt | 5 +- .../coordinators/di/WalletImportModule.kt | 3 +- .../coordinators/fiat/GetBuyQuoteUrlImpl.kt | 2 +- .../coordinators/fiat/GetBuyQuotesImpl.kt | 2 +- .../fiat/GetFiatTransactionsImpl.kt | 2 +- .../referral/CreateReferralImpl.kt | 2 +- .../coordinators/referral/GetRewardsImpl.kt | 2 +- .../data/coordinators/referral/RedeemImpl.kt | 2 +- .../referral/UseReferralCodeImpl.kt | 2 +- .../transaction/SyncTransactionsImpl.kt | 8 +- .../SyncWalletConfigurationImpl.kt | 2 +- .../SyncWalletConfigurationImplTest.kt | 6 +- .../data/repositories/nft/NftRepository.kt | 4 +- .../services/gemapi/GemDeviceApiClient.kt | 33 ++++--- .../gemapi/http/SecurityInterceptor.kt | 16 +++- .../gemapi/http/SecurityInterceptorTest.kt | 96 +++++++++++++++++++ core | 2 +- 17 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 android/data/services/remote-gem/src/test/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptorTest.kt diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/asset/DeviceAssetsSyncService.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/asset/DeviceAssetsSyncService.kt index 87917f446..adc91eb5a 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/asset/DeviceAssetsSyncService.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/asset/DeviceAssetsSyncService.kt @@ -28,9 +28,10 @@ class DeviceAssetsSyncService @Inject constructor( ) { suspend fun sync(walletId: String) { + val walletIdentifier = WalletId(walletId) val preferences = walletPreferencesFactory.create(walletId) val assetIds = gemDeviceApiClient.getAssets( - walletId = walletId, + walletId = walletIdentifier, fromTimestamp = preferences.assetsTimestamp, ).mapNotNull(String::toAssetId) .distinct() @@ -40,7 +41,7 @@ class DeviceAssetsSyncService @Inject constructor( return } - val wallet = walletsRepository.getWallet(WalletId(walletId)).firstOrNull() ?: return + val wallet = walletsRepository.getWallet(walletIdentifier).firstOrNull() ?: return val existingAssetIds = assetsRepository.hasWalletAssets(wallet.id.id, assetIds) val missingAssetIds = assetIds.filterNot(existingAssetIds::contains) diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletImportModule.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletImportModule.kt index 774bd0628..2dea84588 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletImportModule.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletImportModule.kt @@ -15,6 +15,7 @@ import com.gemwallet.android.data.repositories.assets.AssetsRepository import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.data.services.gemapi.GemDeviceApiClient +import com.wallet.core.primitives.WalletId import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -30,7 +31,7 @@ object WalletImportModule { fun provideGetAvailableAssetIds( gemDeviceApiClient: GemDeviceApiClient, ): GetAvailableAssetIds = GetAvailableAssetIds { walletId -> - gemDeviceApiClient.getAssets(walletId = walletId, fromTimestamp = 0) + gemDeviceApiClient.getAssets(walletId = WalletId(walletId), fromTimestamp = 0) } @Provides diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuoteUrlImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuoteUrlImpl.kt index a596f1685..57e40a6b7 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuoteUrlImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuoteUrlImpl.kt @@ -13,7 +13,7 @@ class GetBuyQuoteUrlImpl( override suspend fun invoke(quoteId: String, walletId: WalletId): String? { return try { gemDeviceApiClient.getFiatQuoteUrl( - walletId = walletId.id, + walletId = walletId, quoteId = quoteId, )?.redirectUrl } catch (_: Exception) { diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuotesImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuotesImpl.kt index 70bfb0fc9..ab1bf3d99 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuotesImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetBuyQuotesImpl.kt @@ -27,7 +27,7 @@ class GetBuyQuotesImpl( assetId = asset.id.toIdentifier(), amount = amount, currency = fiatCurrency, - walletId = walletId.id, + walletId = walletId, type = type.string, )?.quotes ?: throw IOException() } catch (err: Exception) { diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetFiatTransactionsImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetFiatTransactionsImpl.kt index 53416b2ee..a7ec9e61c 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetFiatTransactionsImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/fiat/GetFiatTransactionsImpl.kt @@ -9,6 +9,6 @@ class GetFiatTransactionsImpl( private val gemDeviceApiClient: GemDeviceApiClient, ) : GetFiatTransactions { override suspend fun invoke(walletId: WalletId): List { - return gemDeviceApiClient.getFiatTransactions(walletId.id) + return gemDeviceApiClient.getFiatTransactions(walletId) } } diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/CreateReferralImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/CreateReferralImpl.kt index 9acd1a05f..522092b7a 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/CreateReferralImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/CreateReferralImpl.kt @@ -23,7 +23,7 @@ class CreateReferralImpl( val account = wallet.getAccount(Chain.referralChain) ?: throw ReferralError.BadWallet val authPayload = getAuthPayload.getAuthPayload(wallet, account.chain) return gemDeviceApiClient.createReferral( - walletId = wallet.id.id, + walletId = wallet.id, body = AuthenticatedRequest( auth = authPayload, data = ReferralCode( diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/GetRewardsImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/GetRewardsImpl.kt index 3165f0217..18832d7e7 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/GetRewardsImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/GetRewardsImpl.kt @@ -10,7 +10,7 @@ class GetRewardsImpl( private val gemDeviceApiClient: GemDeviceApiClient, ) : GetRewards { override suspend fun getRewards(walletId: WalletId): Rewards { - val response = gemDeviceApiClient.getRewards(walletId.id) + val response = gemDeviceApiClient.getRewards(walletId) if (response?.code == null) { throw ReferralError.NotCreated } diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/RedeemImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/RedeemImpl.kt index c59a2e1e7..00b4606d6 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/RedeemImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/RedeemImpl.kt @@ -31,7 +31,7 @@ class RedeemImpl( throw ReferralError.InsufficientPoints } val result = gemDeviceApiClient.redeem( - walletId = wallet.id.id, + walletId = wallet.id, request = AuthenticatedRequest( auth = authPayload, data = RedemptionRequest(option.id) diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/UseReferralCodeImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/UseReferralCodeImpl.kt index bdd2246b6..7332b27e6 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/UseReferralCodeImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/referral/UseReferralCodeImpl.kt @@ -21,7 +21,7 @@ class UseReferralCodeImpl( val account = wallet.getAccount(Chain.referralChain) ?: throw ReferralError.BadWallet val auth = getAuthPayload.getAuthPayload(wallet, account.chain) gemDeviceApiClient.useReferralCode( - walletId = wallet.id.id, + walletId = wallet.id, body = AuthenticatedRequest( auth = auth, data = ReferralCode(code) diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/transaction/SyncTransactionsImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/transaction/SyncTransactionsImpl.kt index e472b9c37..63fb64dd8 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/transaction/SyncTransactionsImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/transaction/SyncTransactionsImpl.kt @@ -29,8 +29,8 @@ class SyncTransactionsImpl @Inject constructor( ) : SyncTransactions, SyncAssetTransactions { override suspend fun syncTransactions(wallet: Wallet) { - val walletId = wallet.id.id - val preferences = walletPreferencesFactory.create(walletId) + val walletId = wallet.id + val preferences = walletPreferencesFactory.create(walletId.id) val response = runCatching { gemDeviceApiClient.getTransactions(walletId, preferences.transactionsTimestamp) }.getOrNull() ?: return @@ -46,8 +46,8 @@ class SyncTransactionsImpl @Inject constructor( } private suspend fun syncAssetTransactions(wallet: Wallet, assetId: AssetId) { - val walletId = wallet.id.id - val preferences = walletPreferencesFactory.create(walletId) + val walletId = wallet.id + val preferences = walletPreferencesFactory.create(walletId.id) val assetIdentifier = assetId.identifier val timestamp = preferences.transactionsForAssetTimestamp(assetIdentifier) val response = runCatching { diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImpl.kt index 9bf258319..370f08a44 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImpl.kt @@ -18,7 +18,7 @@ class SyncWalletConfigurationImpl( val preferences = walletPreferencesFactory.create(walletId.id) if (preferences.completeInitialWalletConfiguration) return - val configuration = runCatching { gemDeviceApiClient.getWalletConfiguration(walletId.id).configuration } + val configuration = runCatching { gemDeviceApiClient.getWalletConfiguration(walletId).configuration } .getOrNull() ?: return configuration.multiSignatureAccounts.forEach { account -> diff --git a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImplTest.kt b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImplTest.kt index 7302e67b4..2fac5f5b7 100644 --- a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImplTest.kt +++ b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet_import/SyncWalletConfigurationImplTest.kt @@ -38,7 +38,7 @@ class SyncWalletConfigurationImplTest { @Test fun sync_addsMultisigBannerForEachReturnedAccountAndMarksComplete() = runTest { - coEvery { gemDeviceApiClient.getWalletConfiguration("wallet-1") } returns WalletConfigurationResult( + coEvery { gemDeviceApiClient.getWalletConfiguration(walletId) } returns WalletConfigurationResult( walletId = walletId, configuration = WalletConfiguration( multiSignatureAccounts = listOf( @@ -71,7 +71,7 @@ class SyncWalletConfigurationImplTest { @Test fun sync_doesNothingWhenMultiSignatureAccountsIsEmpty() = runTest { - coEvery { gemDeviceApiClient.getWalletConfiguration("wallet-1") } returns WalletConfigurationResult( + coEvery { gemDeviceApiClient.getWalletConfiguration(walletId) } returns WalletConfigurationResult( walletId = walletId, configuration = WalletConfiguration(multiSignatureAccounts = emptyList()), ) @@ -84,7 +84,7 @@ class SyncWalletConfigurationImplTest { @Test fun sync_swallowsApiFailuresAndDoesNotMarkComplete() = runTest { - coEvery { gemDeviceApiClient.getWalletConfiguration("wallet-1") } throws RuntimeException("network down") + coEvery { gemDeviceApiClient.getWalletConfiguration(walletId) } throws RuntimeException("network down") subject.sync(walletId) diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/nft/NftRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/nft/NftRepository.kt index 8068fc635..42d621825 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/nft/NftRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/nft/NftRepository.kt @@ -35,7 +35,7 @@ class NftRepository( @Throws(HttpException::class, IOException::class) override suspend fun sync(walletId: WalletId) { - val nftData = gemDeviceApiClient.getNFTs(walletId = walletId.id).orEmpty() + val nftData = gemDeviceApiClient.getNFTs(walletId = walletId).orEmpty() val collections = nftData.map { it.collection.toDb() } val assets = nftData.flatMap { it.assets }.map { it.toDb() } val associations = assets.map { DbNFTAssociation(walletId = walletId.id, assetId = it.id) } @@ -44,7 +44,7 @@ class NftRepository( @Throws(HttpException::class, IOException::class) override suspend fun refreshNftAsset(wallet: Wallet, assetId: NFTAssetId) { - gemDeviceApiClient.refreshNftAsset(wallet.id.id, assetId.toIdentifier()) + gemDeviceApiClient.refreshNftAsset(wallet.id, assetId.toIdentifier()) } override fun getListNft(walletId: WalletId, collectionId: String?): Flow> { diff --git a/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/GemDeviceApiClient.kt b/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/GemDeviceApiClient.kt index d06f1da66..c450b6d95 100644 --- a/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/GemDeviceApiClient.kt +++ b/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/GemDeviceApiClient.kt @@ -21,19 +21,18 @@ import com.wallet.core.primitives.Rewards import com.wallet.core.primitives.ScanTransaction import com.wallet.core.primitives.ScanTransactionPayload import com.wallet.core.primitives.Transaction +import com.wallet.core.primitives.WalletId import com.wallet.core.primitives.WalletSubscription import com.wallet.core.primitives.WalletSubscriptionChains import com.wallet.core.primitives.WalletConfigurationResult import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.HTTP -import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Path import retrofit2.http.Query - -const val WALLET_ID_HEADER = "x-wallet-id" +import retrofit2.http.Tag interface GemDeviceApiClient { @@ -76,33 +75,33 @@ interface GemDeviceApiClient { // Rewards @GET("/v2/devices/rewards") - suspend fun getRewards(@Header(WALLET_ID_HEADER) walletId: String): Rewards? + suspend fun getRewards(@Tag walletId: WalletId): Rewards? @GET("/v2/devices/rewards/events") - suspend fun getRewardsEvents(@Header(WALLET_ID_HEADER) walletId: String): List + suspend fun getRewardsEvents(@Tag walletId: WalletId): List @GET("/v2/devices/rewards/redemptions/{code}") suspend fun getRedemptionOption(@Path("code") code: String): RewardRedemptionOption @POST("/v2/devices/rewards/referrals/create") - suspend fun createReferral(@Header(WALLET_ID_HEADER) walletId: String, @Body body: AuthenticatedRequest): Rewards? + suspend fun createReferral(@Tag walletId: WalletId, @Body body: AuthenticatedRequest): Rewards? @POST("/v2/devices/rewards/referrals/use") - suspend fun useReferralCode(@Header(WALLET_ID_HEADER) walletId: String, @Body body: AuthenticatedRequest): Boolean + suspend fun useReferralCode(@Tag walletId: WalletId, @Body body: AuthenticatedRequest): Boolean @POST("/v2/devices/rewards/redeem") - suspend fun redeem(@Header(WALLET_ID_HEADER) walletId: String, @Body request: AuthenticatedRequest): RedemptionResult + suspend fun redeem(@Tag walletId: WalletId, @Body request: AuthenticatedRequest): RedemptionResult // Transactions @GET("/v2/devices/transactions") suspend fun getTransactions( - @Header(WALLET_ID_HEADER) walletId: String, + @Tag walletId: WalletId, @Query("from_timestamp") from: Long, ): TransactionsResponse? @GET("/v2/devices/transactions") suspend fun getTransactions( - @Header(WALLET_ID_HEADER) walletId: String, + @Tag walletId: WalletId, @Query("asset_id") assetId: String, @Query("from_timestamp") from: Long, ): TransactionsResponse? @@ -114,21 +113,21 @@ interface GemDeviceApiClient { suspend fun getScanTransaction(@Body payload: ScanTransactionPayload): ScanTransaction @GET("/v2/devices/wallet_configuration") - suspend fun getWalletConfiguration(@Header(WALLET_ID_HEADER) walletId: String): WalletConfigurationResult + suspend fun getWalletConfiguration(@Tag walletId: WalletId): WalletConfigurationResult // Assets @GET("/v2/devices/assets") - suspend fun getAssets(@Header(WALLET_ID_HEADER) walletId: String, @Query("from_timestamp") fromTimestamp: Long): List + suspend fun getAssets(@Tag walletId: WalletId, @Query("from_timestamp") fromTimestamp: Long): List // NFT @GET("/v2/devices/nft_assets") - suspend fun getNFTs(@Header(WALLET_ID_HEADER) walletId: String): List? + suspend fun getNFTs(@Tag walletId: WalletId): List? @GET("/v2/devices/nft_assets/{asset_id}") suspend fun getNFT(@Path("asset_id") assetId: String): NFTAssetData @POST("/v2/devices/nft_assets/{asset_id}/refresh") - suspend fun refreshNftAsset(@Header(WALLET_ID_HEADER) walletId: String, @Path("asset_id") assetId: String): Boolean + suspend fun refreshNftAsset(@Tag walletId: WalletId, @Path("asset_id") assetId: String): Boolean // AUTH @GET("/v2/devices/auth/nonce") @@ -143,7 +142,7 @@ interface GemDeviceApiClient { @GET("/v2/devices/fiat/quotes/{type}/{asset_id}") suspend fun getFiatQuotes( - @Header(WALLET_ID_HEADER) walletId: String, + @Tag walletId: WalletId, @Path("type") type: String, @Path("asset_id") assetId: String, @Query("amount") amount: Double, @@ -152,13 +151,13 @@ interface GemDeviceApiClient { @GET("/v2/devices/fiat/quotes/{quote_id}/url") suspend fun getFiatQuoteUrl( - @Header(WALLET_ID_HEADER) walletId: String, + @Tag walletId: WalletId, @Path("quote_id") quoteId: String ): FiatQuoteUrl? @GET("/v2/devices/fiat/transactions") suspend fun getFiatTransactions( - @Header(WALLET_ID_HEADER) walletId: String, + @Tag walletId: WalletId, ): List } diff --git a/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptor.kt b/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptor.kt index b41567c1f..f61d03e27 100644 --- a/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptor.kt +++ b/android/data/services/remote-gem/src/main/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptor.kt @@ -1,25 +1,31 @@ package com.gemwallet.android.data.services.gemapi.http import com.gemwallet.android.application.device.coordinators.GetDeviceId +import com.wallet.core.primitives.WalletId import okhttp3.Interceptor import okhttp3.Protocol import okhttp3.Response import okio.Buffer -class SecurityInterceptor( - private val getDeviceId: GetDeviceId, +class SecurityInterceptor internal constructor( + private val signer: DeviceRequestSigner, ) : Interceptor { - private val signer = DeviceRequestSigner(getDeviceId) + constructor(getDeviceId: GetDeviceId) : this(DeviceRequestSigner(getDeviceId)) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val body = request.body?.let { val buffer = Buffer() it.writeTo(buffer) - buffer.readUtf8().toByteArray() + buffer.readByteArray() } - val signature = signer.sign(request.method, request.url.encodedPath, body) + val signature = signer.sign( + method = request.method, + path = request.url.encodedPath, + body = body, + walletId = request.tag(WalletId::class.java)?.id.orEmpty(), + ) return try { val builder = request.newBuilder() signature.toHeaders().forEach { (key, value) -> builder.header(key, value) } diff --git a/android/data/services/remote-gem/src/test/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptorTest.kt b/android/data/services/remote-gem/src/test/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptorTest.kt new file mode 100644 index 000000000..476cab768 --- /dev/null +++ b/android/data/services/remote-gem/src/test/kotlin/com/gemwallet/android/data/services/gemapi/http/SecurityInterceptorTest.kt @@ -0,0 +1,96 @@ +package com.gemwallet.android.data.services.gemapi.http + +import com.gemwallet.android.application.device.coordinators.GetDeviceId +import com.wallet.core.primitives.WalletId +import java.util.Base64 +import java.util.concurrent.TimeUnit +import okhttp3.Call +import okhttp3.Connection +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class SecurityInterceptorTest { + + @Test + fun interceptSignsWalletIdFromRequestTagAndBody() { + var hashedBody: ByteArray? = null + val signer = DeviceRequestSigner( + getDeviceId = FakeSecurityGetDeviceId( + deviceId = "publickeyhex", + deviceKey = "privatekeyhex", + ), + bodyHash = { body -> + hashedBody = body + "bodyhash" + }, + signMessage = { _, _ -> "signaturehex" }, + currentTimeMillis = { 123L }, + ) + val body = """{"device":"android"}""" + val request = Request.Builder() + .url("https://api.gemwallet.com/v2/devices/rewards") + .post(body.toRequestBody("application/json".toMediaType())) + .tag(WalletId::class.java, WalletId("multicoin_0xabc")) + .build() + + val chain = FakeChain(request) + SecurityInterceptor(signer).intercept(chain) + val signedRequest = chain.proceededRequest!! + val authorization = signedRequest.header("Authorization")!! + val payload = String(Base64.getDecoder().decode(authorization.removePrefix("Gem "))) + + assertEquals("publickeyhex.123.multicoin_0xabc.bodyhash.signaturehex", payload) + assertNull(signedRequest.header("x-wallet-id")) + assertEquals(body, hashedBody!!.toString(Charsets.UTF_8)) + } +} + +private class FakeChain( + private val request: Request, +) : Interceptor.Chain { + var proceededRequest: Request? = null + + override fun request(): Request = request + + override fun proceed(request: Request): Response { + proceededRequest = request + return Response.Builder() + .request(request) + .protocol(Protocol.HTTP_2) + .code(200) + .message("OK") + .build() + } + + override fun connection(): Connection? = null + + override fun call(): Call = error("not used") + + override fun connectTimeoutMillis(): Int = 0 + + override fun withConnectTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = this + + override fun readTimeoutMillis(): Int = 0 + + override fun withReadTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = this + + override fun writeTimeoutMillis(): Int = 0 + + override fun withWriteTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = this +} + +private class FakeSecurityGetDeviceId( + private val deviceId: String, + private val deviceKey: String, +) : GetDeviceId { + override fun getDeviceId(): String = deviceId + + override fun getDeviceKey(): String = deviceKey +} diff --git a/core b/core index d92723d89..b753ccfe6 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit d92723d8919e36c91f7c685fe5f7a5f33f22fa8b +Subproject commit b753ccfe681f1ec761c8cf01bda995ae19547659