Skip to content

Commit

Permalink
feat: expose authCheck etc to enable RN init flow (#64)
Browse files Browse the repository at this point in the history
* feat: expose authCheck etc to enable RN init flow

* add try catch around push notifications so the example app renders

* make sure it has a v1 key

---------

Co-authored-by: Naomi Plasterer <[email protected]>
  • Loading branch information
dmccartney and nplasterer authored Apr 11, 2023
1 parent bae82f2 commit 5747cba
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,22 @@ object PushNotificationTokenManager {
}

fun ensurePushTokenIsConfigured() {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { request ->
if (!request.isSuccessful) {
Log.e(TAG, "Firebase getInstanceId() failed", request.exception)
return@OnCompleteListener
}
request.result?.let {
if (xmtpPushState.value is XMTPPushState.Ready) {
xmtpPush.register(it)
configureNotificationChannels()
try {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { request ->
if (!request.isSuccessful) {
Log.e(TAG, "Firebase getInstanceId() failed", request.exception)
return@OnCompleteListener
}
request.result?.let {
if (xmtpPushState.value is XMTPPushState.Ready) {
xmtpPush.register(it)
configureNotificationChannels()
}
}
}
})
})
} catch (e: Exception) {
Log.e(TAG, "Firebase not setup", e)
}
}

internal fun syncPushNotificationsToken(token: String) {
Expand Down
76 changes: 55 additions & 21 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.xmtp.android.library

import android.os.Build
import android.util.Log
import com.google.crypto.tink.subtle.Base64
import com.google.gson.GsonBuilder
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking
import org.web3j.crypto.Keys
import org.web3j.crypto.Keys.toChecksumAddress
import org.xmtp.android.library.codecs.ContentCodec
import org.xmtp.android.library.codecs.TextCodec
import org.xmtp.android.library.messages.ContactBundle
Expand Down Expand Up @@ -52,6 +54,8 @@ class Client() {
lateinit var conversations: Conversations

companion object {
private const val TAG = "Client"

var codecRegistry = run {
val registry = CodecRegistry()
registry.register(codec = TextCodec())
Expand All @@ -61,6 +65,49 @@ class Client() {
fun register(codec: ContentCodec<*>) {
codecRegistry.register(codec = codec)
}

/**
* Use the {@param api} to fetch any stored keys belonging to {@param address}.
*
* The user will need to be prompted to sign to decrypt each bundle.
*/
suspend fun authCheck(api: ApiClient, address: String): List<EncryptedPrivateKeyBundle> {
val topic = Topic.userPrivateStoreKeyBundle(toChecksumAddress(address))
val res = api.queryTopic(topic)
return res.envelopesList.mapNotNull {
try {
EncryptedPrivateKeyBundle.parseFrom(it.message)
} catch (e: Exception) {
Log.e(TAG, "discarding malformed private key bundle: ${e.message}", e)
null
}
}
}

/**
* Use the {@param api} to save the {@param encryptedKeys} for {@param address}.
*
* The {@param keys} are used to authorize the publish request.
*/
suspend fun authSave(
api: ApiClient,
v1Key: PrivateKeyBundleV1,
encryptedKeys: EncryptedPrivateKeyBundle,
) {
val authorizedIdentity = AuthorizedIdentity(v1Key)
authorizedIdentity.address = v1Key.walletAddress
val authToken = authorizedIdentity.createAuthToken()
api.setAuthToken(authToken)
api.publish(
envelopes = listOf(
EnvelopeBuilder.buildFromTopic(
topic = Topic.userPrivateStoreKeyBundle(v1Key.walletAddress),
timestamp = Date(),
message = encryptedKeys.toByteArray()
)
)
)
}
}

constructor(
Expand Down Expand Up @@ -119,38 +166,24 @@ class Client() {
apiClient: ApiClient,
): PrivateKeyBundleV1 {
val keys = loadPrivateKeys(account, apiClient)
if (keys != null) {
return keys
return if (keys != null) {
keys
} else {
val v1Keys = PrivateKeyBundleV1.newBuilder().build().generate(account)
val keyBundle = PrivateKeyBundleBuilder.buildFromV1Key(v1Keys)
val encryptedKeys = keyBundle.encrypted(account)
val authorizedIdentity = AuthorizedIdentity(privateKeyBundleV1 = v1Keys)
authorizedIdentity.address = account.address
val authToken = authorizedIdentity.createAuthToken()
apiClient.setAuthToken(authToken)
apiClient.publish(
envelopes = listOf(
EnvelopeBuilder.buildFromTopic(
topic = Topic.userPrivateStoreKeyBundle(account.address),
timestamp = Date(),
message = encryptedKeys.toByteArray()
)
)
)
return v1Keys
authSave(apiClient, keyBundle.v1, encryptedKeys)
v1Keys
}
}

private suspend fun loadPrivateKeys(
account: SigningKey,
apiClient: ApiClient,
): PrivateKeyBundleV1? {
val topic: Topic = Topic.userPrivateStoreKeyBundle(account.address)
val res = apiClient.queryTopic(topic = topic)
for (envelope in res.envelopesList) {
val encryptedBundles = authCheck(apiClient, account.address)
for (encryptedBundle in encryptedBundles) {
try {
val encryptedBundle = EncryptedPrivateKeyBundle.parseFrom(envelope.message)
val bundle = encryptedBundle.decrypted(account)
return bundle.v1
} catch (e: Throwable) {
Expand Down Expand Up @@ -178,7 +211,8 @@ class Client() {
}
val contactBundle = ContactBundle.newBuilder().also {
it.v2Builder.keyBundle = keys.getPublicKeyBundle()
it.v2Builder.keyBundleBuilder.identityKeyBuilder.signature = it.v2.keyBundle.identityKey.signature.ensureWalletSignature()
it.v2Builder.keyBundleBuilder.identityKeyBuilder.signature =
it.v2.keyBundle.identityKey.signature.ensureWalletSignature()
}.build()
val envelope = MessageApiOuterClass.Envelope.newBuilder().apply {
contentTopic = Topic.contact(address).description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ fun Signature.enableIdentityText(key: ByteArray): String =
("XMTP : Enable Identity\n" + "${key.toHex()}\n" + "\n" + "For more info: https://xmtp.org/signatures/")

val Signature.rawData: ByteArray
get() = ecdsaCompact.bytes.toByteArray() + ecdsaCompact.recovery.toByte()
get() = if (hasEcdsaCompact()) {
ecdsaCompact.bytes.toByteArray() + ecdsaCompact.recovery.toByte()
} else {
walletEcdsaCompact.bytes.toByteArray() + walletEcdsaCompact.recovery.toByte()
}

val Signature.rawDataWithNormalizedRecovery: ByteArray
get() {
Expand Down

0 comments on commit 5747cba

Please sign in to comment.