Skip to content

Commit 55f005b

Browse files
committed
feat: integrate AppCheck for integrity/attestation antispam
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent a9885bf commit 55f005b

8 files changed

Lines changed: 104 additions & 13 deletions

File tree

api/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ dependencies {
6363
implementation(Libs.okhttp)
6464
implementation(Libs.mixpanel)
6565

66+
implementation(platform(Libs.firebase_bom))
67+
implementation(Libs.firebase_appcheck)
68+
6669
implementation(Libs.androidx_paging_runtime)
6770

6871
kapt(Libs.androidx_room_compiler)

api/src/main/java/com/getcode/model/intents/IntentType.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.getcode.ed25519.Ed25519
55
import com.getcode.solana.keys.Signature
66
import com.getcode.model.intents.actions.ActionType
77
import com.getcode.model.intents.actions.numberActions
8+
import com.getcode.network.appcheck.toDeviceToken
89
import com.getcode.network.repository.*
910
import com.getcode.solana.keys.PublicKey
1011

@@ -42,14 +43,18 @@ abstract class IntentType {
4243
.build()
4344
}
4445

45-
fun requestToSubmitActions(owner: Ed25519.KeyPair): TransactionService.SubmitIntentRequest {
46+
fun requestToSubmitActions(owner: Ed25519.KeyPair, deviceToken: String? = null): TransactionService.SubmitIntentRequest {
4647
val submitActionsBuilder = TransactionService.SubmitIntentRequest.SubmitActions.newBuilder()
4748
submitActionsBuilder.owner = owner.publicKeyBytes.toSolanaAccount()
4849
submitActionsBuilder.id = id.toIntentId()
4950
submitActionsBuilder.metadata = metadata()
5051
submitActionsBuilder.addAllActions(actionGroup.actions.map { it.action() })
5152
submitActionsBuilder.signature = submitActionsBuilder.sign(owner)
5253

54+
if (deviceToken != null) {
55+
submitActionsBuilder.setDeviceToken(deviceToken.toDeviceToken())
56+
}
57+
5358
return TransactionService.SubmitIntentRequest.newBuilder()
5459
.setSubmitActions(submitActionsBuilder)
5560
.build()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.getcode.network.appcheck
2+
3+
import com.codeinc.gen.common.v1.Model
4+
import com.google.firebase.Firebase
5+
import com.google.firebase.appcheck.AppCheckToken
6+
import com.google.firebase.appcheck.appCheck
7+
import io.reactivex.rxjava3.core.BackpressureStrategy
8+
import io.reactivex.rxjava3.core.Flowable
9+
import io.reactivex.rxjava3.core.Single
10+
import kotlinx.coroutines.flow.Flow
11+
import kotlinx.coroutines.reactive.asFlow
12+
13+
object AppCheck {
14+
15+
@Deprecated("Replace with Flow variant")
16+
fun limitedUseTokenSingle(): Single<AppCheckToken> {
17+
return Single.create { emitter ->
18+
Firebase.appCheck.limitedUseAppCheckToken
19+
.addOnSuccessListener { emitter.onSuccess(it) }
20+
.addOnFailureListener { emitter.onError(it) }
21+
}
22+
}
23+
24+
@Deprecated("Replace with Flow variant")
25+
fun limitedUseTokenFlowable(
26+
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.BUFFER
27+
): Flowable<AppCheckToken> {
28+
return Flowable.create({ emitter ->
29+
Firebase.appCheck.limitedUseAppCheckToken
30+
.addOnSuccessListener { emitter.onNext(it) }
31+
.addOnFailureListener { emitter.onError(it) }
32+
}, backpressureStrategy)
33+
}
34+
35+
fun limitedUseToken(
36+
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.BUFFER
37+
): Flow<AppCheckToken> {
38+
return limitedUseTokenFlowable(backpressureStrategy).asFlow()
39+
}
40+
}
41+
42+
fun AppCheckToken.toDeviceToken() = Model.DeviceToken.newBuilder().setValue(token).build()
43+
fun String.toDeviceToken() = Model.DeviceToken.newBuilder().setValue(this).build()

api/src/main/java/com/getcode/network/repository/PhoneRepository.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.getcode.network.repository
22

3+
import com.codeinc.gen.common.v1.Model.DeviceToken
34
import com.codeinc.gen.phone.v1.PhoneVerificationService
45
import com.getcode.db.Database
56
import com.getcode.db.InMemoryDao
@@ -8,13 +9,30 @@ import com.getcode.model.PrefsBool
89
import com.getcode.model.PrefsString
910
import com.getcode.network.core.NetworkOracle
1011
import com.getcode.network.api.PhoneApi
12+
import com.getcode.network.appcheck.AppCheck
13+
import com.getcode.network.appcheck.toDeviceToken
14+
import com.google.firebase.Firebase
15+
import com.google.firebase.appcheck.AppCheckToken
16+
import com.google.firebase.appcheck.appCheck
17+
import io.reactivex.rxjava3.core.BackpressureStrategy
1118
import io.reactivex.rxjava3.core.Flowable
1219
import io.reactivex.rxjava3.core.Single
1320
import kotlinx.coroutines.flow.MutableStateFlow
1421
import java.io.ByteArrayOutputStream
1522
import javax.inject.Inject
1623
import javax.inject.Singleton
1724

25+
26+
fun appCheckToken(
27+
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.BUFFER
28+
): Flowable<AppCheckToken> {
29+
return Flowable.create({ emitter ->
30+
Firebase.appCheck.limitedUseAppCheckToken
31+
.addOnSuccessListener { emitter.onNext(it) }
32+
.addOnFailureListener { emitter.onError(it) }
33+
}, backpressureStrategy)
34+
}
35+
1836
@Singleton
1937
class PhoneRepository @Inject constructor(
2038
private val phoneApi: PhoneApi,
@@ -37,14 +55,18 @@ class PhoneRepository @Inject constructor(
3755
if (isMock()) return Single.just(PhoneVerificationService.SendVerificationCodeResponse.Result.OK)
3856
.toFlowable()
3957

40-
val request =
41-
PhoneVerificationService.SendVerificationCodeRequest.newBuilder()
42-
.setPhoneNumber(phoneValue.toPhoneNumber())
43-
.build()
58+
return AppCheck.limitedUseTokenFlowable()
59+
.flatMap { tokenResult ->
60+
val request =
61+
PhoneVerificationService.SendVerificationCodeRequest.newBuilder()
62+
.setPhoneNumber(phoneValue.toPhoneNumber())
63+
.setDeviceToken(tokenResult.toDeviceToken())
64+
.build()
4465

45-
return phoneApi.sendVerificationCode(request)
46-
.map { it.result }
47-
.let { networkOracle.managedRequest(it) }
66+
phoneApi.sendVerificationCode(request)
67+
.map { it.result }
68+
.let { networkOracle.managedRequest(it) }
69+
}
4870
}
4971

5072
fun checkVerificationCode(
@@ -68,7 +90,7 @@ class PhoneRepository @Inject constructor(
6890
): Flowable<GetAssociatedPhoneNumberResponse> {
6991
if (isMock()) {
7092
return Flowable.just(
71-
GetAssociatedPhoneNumberResponse(true, true, false,"+12223334455")
93+
GetAssociatedPhoneNumberResponse(true, true, false, "+12223334455")
7294
)
7395
}
7496

api/src/main/java/com/getcode/network/repository/TransactionRepository.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.getcode.model.intents.IntentType
2424
import com.getcode.model.intents.IntentUpgradePrivacy
2525
import com.getcode.model.intents.ServerParameter
2626
import com.getcode.network.api.TransactionApiV2
27+
import com.getcode.network.appcheck.AppCheck
2728
import com.getcode.solana.keys.AssociatedTokenAccount
2829
import com.getcode.solana.keys.PublicKey
2930
import com.getcode.solana.organizer.AccountType
@@ -86,7 +87,11 @@ class TransactionRepository @Inject constructor(
8687
.delay(1, TimeUnit.SECONDS)
8788

8889
val createAccounts = IntentCreateAccounts.newInstance(organizer)
89-
return submit(createAccounts, organizer.tray.owner.getCluster().authority.keyPair)
90+
91+
return AppCheck.limitedUseTokenSingle()
92+
.flatMap { tokenResult ->
93+
submit(createAccounts, organizer.tray.owner.getCluster().authority.keyPair, tokenResult.token)
94+
}
9095
}
9196

9297
fun transfer(
@@ -236,7 +241,7 @@ class TransactionRepository @Inject constructor(
236241
return submit(intent, owner = organizer.tray.owner.getCluster().authority.keyPair)
237242
}
238243

239-
private fun submit(intent: IntentType, owner: KeyPair): Single<IntentType> {
244+
private fun submit(intent: IntentType, owner: KeyPair, deviceToken: String? = null): Single<IntentType> {
240245
Timber.i("Submit ${intent.javaClass.simpleName}")
241246
val subject = SingleSubject.create<IntentType>()
242247

@@ -319,7 +324,7 @@ class TransactionRepository @Inject constructor(
319324

320325
// 1. Send `submitActions` request with
321326
// actions generated by the intent
322-
serverMessageStream.onNext(intent.requestToSubmitActions(owner))
327+
serverMessageStream.onNext(intent.requestToSubmitActions(owner, deviceToken))
323328

324329
return subject
325330
}

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ dependencies {
164164

165165
implementation(platform(Libs.firebase_bom))
166166
implementation(Libs.firebase_analytics)
167+
implementation(Libs.firebase_appcheck_playintegrity)
167168

168169
implementation(Libs.hilt_nav_compose)
169170
implementation(Libs.lib_phone_number_port)

app/src/main/java/com/getcode/App.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import com.bugsnag.android.Bugsnag
66
import com.getcode.manager.AuthManager
77
import com.getcode.utils.ErrorUtils
88
import com.getcode.view.main.bill.CashBillAssets
9+
import com.google.firebase.Firebase
10+
import com.google.firebase.app
11+
import com.google.firebase.appcheck.appCheck
12+
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
13+
import com.google.firebase.initialize
914
import dagger.hilt.android.HiltAndroidApp
1015
import io.reactivex.rxjava3.plugins.RxJavaPlugins
1116
import timber.log.Timber
@@ -23,6 +28,11 @@ class App : Application() {
2328

2429
CashBillAssets.load(this)
2530

31+
Firebase.initialize(this)
32+
Firebase.appCheck.installAppCheckProviderFactory(
33+
PlayIntegrityAppCheckProviderFactory.getInstance()
34+
)
35+
2636
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
2737

2838
RxJavaPlugins.setErrorHandler {

buildSrc/src/main/java/Dependencies.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ object Versions {
4444
const val kin_sdk: String = "1.0.1"
4545
const val grpc_android: String = "1.33.1"
4646
const val slf4j: String = "1.7.25"
47-
const val firebase_bom: String = "29.1.0"
47+
const val firebase_bom: String = "32.7.1"
4848
const val crashlytics_gradle: String = "2.8.1"
4949
const val play_service_auth = "20.4.0"
5050
const val play_service_auth_phone = "18.0.1"
@@ -175,6 +175,8 @@ object Libs {
175175

176176
const val firebase_bom = "com.google.firebase:firebase-bom:${Versions.firebase_bom}"
177177
const val firebase_analytics = "com.google.firebase:firebase-analytics-ktx"
178+
const val firebase_appcheck = "com.google.firebase:firebase-appcheck-ktx"
179+
const val firebase_appcheck_playintegrity = "com.google.firebase:firebase-appcheck-playintegrity"
178180
const val firebase_crashlytics = "com.google.firebase:firebase-crashlytics-ktx"
179181
const val firebase_messaging = "com.google.firebase:firebase-messaging-ktx"
180182
const val firebase_perf = "com.google.firebase:firebase-perf-ktx"

0 commit comments

Comments
 (0)