diff --git a/services/flipcash/build.gradle.kts b/services/flipcash/build.gradle.kts index e8fc17062d..069fa3f76b 100644 --- a/services/flipcash/build.gradle.kts +++ b/services/flipcash/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.flipcash.android.library) + alias(libs.plugins.flipcash.android.ed25519.shadow) id("com.google.devtools.ksp") id("org.jetbrains.kotlin.plugin.parcelize") } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/AccountApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/AccountApi.kt index fd6c0b52d6..eb821aabbd 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/AccountApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/AccountApi.kt @@ -7,7 +7,7 @@ import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asCountryCode import com.flipcash.services.internal.network.extensions.asPublicKey import com.flipcash.services.internal.network.extensions.asUserId -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.internal.network.extensions.sign import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi @@ -53,8 +53,7 @@ internal class AccountApi @Inject constructor( suspend fun login(owner: KeyPair): RpcAccountService.LoginResponse { val request = RpcAccountService.LoginRequest.newBuilder() .setTimestamp(Timestamp.newBuilder().setSeconds(System.currentTimeMillis() / 1_000)) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -75,8 +74,7 @@ internal class AccountApi @Inject constructor( .setUserId(userId.asUserId()) .setPlatform(Common.Platform.GOOGLE) .setCountryCode(countryCode.asCountryCode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ActivityFeedApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ActivityFeedApi.kt index 27c4815e4a..eec3ce3bc7 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ActivityFeedApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ActivityFeedApi.kt @@ -6,7 +6,7 @@ import com.codeinc.flipcash.gen.activity.v1.Model import com.codeinc.flipcash.gen.activity.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asQueryOptions -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.internal.network.extensions.toNotificationIds import com.flipcash.services.models.ActivityFeedType import com.flipcash.services.models.QueryOptions @@ -44,8 +44,7 @@ internal class ActivityFeedApi @Inject constructor( val request = ActivityFeedService.GetLatestNotificationsRequest.newBuilder() .setType(Model.ActivityFeedType.forNumber(type.ordinal)) .setMaxItems(maxItems) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -69,8 +68,7 @@ internal class ActivityFeedApi @Inject constructor( val request = ActivityFeedService.GetPagedNotificationsRequest.newBuilder() .setType(Model.ActivityFeedType.forNumber(type.ordinal)) .setQueryOptions(queryOptions.asQueryOptions()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -92,9 +90,7 @@ internal class ActivityFeedApi @Inject constructor( val request = ActivityFeedService.GetBatchNotificationsRequest.newBuilder() .apply { addAllIds(ids.toNotificationIds()) - }.apply { - setAuth(authenticate(owner)) - }.build() + }.buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlobStorageApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlobStorageApi.kt index f8740c37e6..293402d88b 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlobStorageApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlobStorageApi.kt @@ -9,6 +9,7 @@ import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asChatId import com.flipcash.services.internal.network.extensions.asUserId import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.chat.BlobAccessContext import com.flipcash.services.models.chat.BlobId import com.getcode.ed25519.Ed25519 @@ -38,8 +39,7 @@ internal class BlobStorageApi @Inject constructor( suspend fun getUploadPolicy(owner: Ed25519.KeyPair): RpcBlobStorageService.GetUploadPolicyResponse { val request = RpcBlobStorageService.GetUploadPolicyRequest.newBuilder() - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -56,8 +56,7 @@ internal class BlobStorageApi @Inject constructor( val request = RpcBlobStorageService.InitiateExternalUploadRequest.newBuilder() .setMimeType(mimeType) .setSizeBytes(sizeBytes) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -72,8 +71,7 @@ internal class BlobStorageApi @Inject constructor( ): RpcBlobStorageService.CompleteExternalUploadResponse { val request = RpcBlobStorageService.CompleteExternalUploadRequest.newBuilder() .setBlobId(blobId.toProto()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlocklistApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlocklistApi.kt index 829de957a5..15b952436a 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlocklistApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/BlocklistApi.kt @@ -6,7 +6,7 @@ import com.codeinc.flipcash.gen.blocklist.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asQueryOptions import com.flipcash.services.internal.network.extensions.asUserId -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.QueryOptions import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi @@ -34,8 +34,7 @@ internal class BlocklistApi @Inject constructor( suspend fun blockUser(userId: ID, owner: KeyPair): BlocklistService.BlockUserResponse { val request = BlocklistService.BlockUserRequest.newBuilder() .setUserId(userId.asUserId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -51,8 +50,7 @@ internal class BlocklistApi @Inject constructor( suspend fun unblockUser(userId: ID, owner: KeyPair): BlocklistService.UnblockUserResponse { val request = BlocklistService.UnblockUserRequest.newBuilder() .setUserId(userId.asUserId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -67,8 +65,7 @@ internal class BlocklistApi @Inject constructor( suspend fun isBlocked(userId: ID, owner: KeyPair): BlocklistService.IsBlockedResponse { val request = BlocklistService.IsBlockedRequest.newBuilder() .setUserId(userId.asUserId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -87,8 +84,7 @@ internal class BlocklistApi @Inject constructor( ): BlocklistService.GetBlocklistResponse { val request = BlocklistService.GetBlocklistRequest.newBuilder() .setQueryOptions(queryOptions.asQueryOptions()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatApi.kt index 82bf7160eb..c55c83759e 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatApi.kt @@ -11,7 +11,7 @@ import com.flipcash.services.internal.network.extensions.asProtoMuteState import com.flipcash.services.internal.network.extensions.asProtoRules import com.flipcash.services.internal.network.extensions.asQueryOptions import com.flipcash.services.internal.network.extensions.asViewMode -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.QueryOptions import com.flipcash.services.models.chat.ChatId import com.flipcash.services.models.chat.ChatType @@ -46,8 +46,7 @@ internal class ChatApi @Inject constructor( val request = RpcChatService.GetChatRequest.newBuilder() .setChatId(chatId.asChatId()) .setViewMode(viewMode.asViewMode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -64,8 +63,7 @@ internal class ChatApi @Inject constructor( val request = RpcChatService.GetDmChatFeedRequest.newBuilder() .setQueryOptions(queryOptions.asQueryOptions()) .setDmChatType(chatType.asProtoChatType()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -80,8 +78,7 @@ internal class ChatApi @Inject constructor( ): RpcChatService.GetGroupChatFeedResponse { val request = RpcChatService.GetGroupChatFeedRequest.newBuilder() .setQueryOptions(queryOptions.asQueryOptions()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -119,8 +116,7 @@ internal class ChatApi @Inject constructor( val request = requestBuilder .setIdempotencyKey(idempotencyKey.asProtoIdempotencyKey()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -135,8 +131,7 @@ internal class ChatApi @Inject constructor( ): RpcChatService.JoinChatResponse { val request = RpcChatService.JoinChatRequest.newBuilder() .setChatId(chatId.asChatId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -151,8 +146,7 @@ internal class ChatApi @Inject constructor( ): RpcChatService.LeaveChatResponse { val request = RpcChatService.LeaveChatRequest.newBuilder() .setChatId(chatId.asChatId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -169,8 +163,7 @@ internal class ChatApi @Inject constructor( val request = RpcChatService.MuteChatRequest.newBuilder() .setChatId(chatId.asChatId()) .setMute(mute.asProtoMuteState()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -185,8 +178,7 @@ internal class ChatApi @Inject constructor( ): RpcChatService.UnmuteChatResponse { val request = RpcChatService.UnmuteChatRequest.newBuilder() .setChatId(chatId.asChatId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatMessagingApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatMessagingApi.kt index 70222b90b9..ab63661962 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatMessagingApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ChatMessagingApi.kt @@ -14,7 +14,7 @@ import com.flipcash.services.internal.network.extensions.asPointerType import com.flipcash.services.internal.network.extensions.asQueryOptions import com.flipcash.services.internal.network.extensions.asTypingState import com.flipcash.services.internal.network.extensions.asViewMode -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.QueryOptions import com.flipcash.services.models.chat.ChatId import com.flipcash.services.models.chat.ClientMessageId @@ -54,8 +54,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setMessageId(MessagingModel.MessageId.newBuilder().setValue(messageId)) .setViewMode(viewMode.asViewMode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -74,8 +73,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setOptions(queryOptions.asQueryOptions()) .setViewMode(viewMode.asViewMode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -97,8 +95,7 @@ internal class ChatMessagingApi @Inject constructor( .addAllMessageIds(messageIds.map { MessagingModel.MessageId.newBuilder().setValue(it).build() }) ) .setViewMode(viewMode.asViewMode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -117,8 +114,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .addAllContent(content.map { it.asContent() }) .setClientMessageId(clientMessageId.asClientMessageId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -138,8 +134,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setPointerType(pointerType.asPointerType()) .setNewValue(MessagingModel.MessageId.newBuilder().setValue(messageId)) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -156,8 +151,7 @@ internal class ChatMessagingApi @Inject constructor( val request = RpcMessagingService.NotifyIsTypingRequest.newBuilder() .setChatId(chatId.asChatId()) .setState(state.asTypingState()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -176,8 +170,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setAfterSequence(afterSequence) .setViewMode(viewMode.asViewMode()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -196,8 +189,7 @@ internal class ChatMessagingApi @Inject constructor( .setMessageId(messageId.asMessageId()) .addAllContent(content.map { it.asContent() }) .setExpectedEventSequence(expectedEventSequence) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -216,8 +208,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setMessageId(messageId.asMessageId()) .setExpectedEventSequence(expectedEventSequence) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -236,8 +227,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setMessageId(messageId.asMessageId()) .setEmoji(emoji.asEmoji()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -256,8 +246,7 @@ internal class ChatMessagingApi @Inject constructor( .setChatId(chatId.asChatId()) .setMessageId(messageId.asMessageId()) .setEmoji(emoji.asEmoji()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -278,8 +267,7 @@ internal class ChatMessagingApi @Inject constructor( .setMessageId(messageId.asMessageId()) .setEmoji(emoji.asEmoji()) .setOptions(queryOptions.asQueryOptions()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -296,8 +284,7 @@ internal class ChatMessagingApi @Inject constructor( val request = RpcMessagingService.GetReactionSummaryRequest.newBuilder() .setChatId(chatId.asChatId()) .setMessageId(messageId.asMessageId()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -314,8 +301,7 @@ internal class ChatMessagingApi @Inject constructor( val request = RpcMessagingService.GetReactionSummariesRequest.newBuilder() .setChatId(chatId.asChatId()) .setOptions(queryOptions.asQueryOptions()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ContactListApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ContactListApi.kt index 46dfb6272d..95169ccb0a 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ContactListApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ContactListApi.kt @@ -6,7 +6,7 @@ import com.codeinc.flipcash.gen.contact.v1.validate import com.flipcash.services.models.ContactMethod import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asHash -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi import com.getcode.solana.keys.Checksum @@ -34,8 +34,7 @@ internal class ContactListApi @Inject constructor( ): RpcContactListService.CheckSyncResponse { val request = RpcContactListService.CheckSyncRequest.newBuilder() .setClientChecksum(clientChecksum.asHash()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -56,8 +55,7 @@ internal class ContactListApi @Inject constructor( .addAllRemoves(removes.map { Common.PhoneNumber.newBuilder().setValue(it.phoneNumber).build() }) .setOldChecksum(oldChecksum.asHash()) .setNewChecksum(newChecksum.asHash()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -76,8 +74,7 @@ internal class ContactListApi @Inject constructor( val request = RpcContactListService.FullUploadRequest.newBuilder() .addAllPhones(batch.map { Common.PhoneNumber.newBuilder().setValue(it.phoneNumber).build() }) .setExpectedChecksum(expectedChecksum.asHash()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() emit(request) @@ -95,8 +92,7 @@ internal class ContactListApi @Inject constructor( ): Flow { val request = RpcContactListService.GetFlipcashContactsRequest.newBuilder() .setChecksum(checksum.asHash()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } return api.getFlipcashContacts(request) } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/EmailVerificationApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/EmailVerificationApi.kt index e8ca56740d..e357875a19 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/EmailVerificationApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/EmailVerificationApi.kt @@ -5,7 +5,7 @@ import com.codeinc.flipcash.gen.email.v1.EmailVerificationService import com.codeinc.flipcash.gen.email.v1.Model import com.codeinc.flipcash.gen.common.v1.Common import com.flipcash.services.internal.annotations.FlipcashManagedChannel -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.ContactMethod import com.getcode.ed25519.Ed25519 import com.getcode.opencode.internal.network.core.GrpcApi @@ -38,8 +38,7 @@ internal class EmailVerificationApi @Inject constructor( val request = EmailVerificationService.SendVerificationCodeRequest.newBuilder() .setEmailAddress(Common.EmailAddress.newBuilder().setValue(request.emailAddress).build()) .setClientData(request.clientData) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -59,8 +58,7 @@ internal class EmailVerificationApi @Inject constructor( val request = EmailVerificationService.CheckVerificationCodeRequest.newBuilder() .setEmailAddress(Common.EmailAddress.newBuilder().setValue(request.emailAddress).build()) .setCode(Model.VerificationCode.newBuilder().setValue(code).build()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -75,8 +73,7 @@ internal class EmailVerificationApi @Inject constructor( ): EmailVerificationService.UnlinkResponse { val request = EmailVerificationService.UnlinkRequest.newBuilder() .setEmailAddress(Common.EmailAddress.newBuilder().setValue(request.emailAddress).build()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ModerationApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ModerationApi.kt index d5a28fb07b..7eb85c8c88 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ModerationApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ModerationApi.kt @@ -4,7 +4,7 @@ import com.codeinc.flipcash.gen.moderation.v1.ModerationGrpcKt import com.codeinc.flipcash.gen.moderation.v1.ModerationService import com.codeinc.flipcash.gen.moderation.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519 import com.getcode.opencode.internal.network.core.GrpcApi import com.getcode.utils.toByteString @@ -26,8 +26,7 @@ internal class ModerationApi @Inject constructor( suspend fun moderateText(text: String, owner: Ed25519.KeyPair): ModerationService.ModerateTextResponse { val request = ModerationService.ModerateTextRequest.newBuilder() .setText(text) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -39,8 +38,7 @@ internal class ModerationApi @Inject constructor( suspend fun moderateImage(imageBytes: ByteArray, owner: Ed25519.KeyPair): ModerationService.ModerateImageResponse { val request = ModerationService.ModerateImageRequest.newBuilder() .setImageData(imageBytes.toByteString()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PhoneVerificationApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PhoneVerificationApi.kt index 0cf8fc42a3..d7a6789081 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PhoneVerificationApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PhoneVerificationApi.kt @@ -5,7 +5,7 @@ import com.codeinc.flipcash.gen.phone.v1.Model import com.codeinc.flipcash.gen.phone.v1.PhoneVerificationGrpcKt import com.codeinc.flipcash.gen.phone.v1.PhoneVerificationService import com.flipcash.services.internal.annotations.FlipcashManagedChannel -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.ContactMethod import com.getcode.ed25519.Ed25519 import com.getcode.opencode.internal.network.core.GrpcApi @@ -38,8 +38,7 @@ internal class PhoneVerificationApi @Inject constructor( val request = PhoneVerificationService.SendVerificationCodeRequest.newBuilder() .setPhoneNumber(Common.PhoneNumber.newBuilder().setValue(request.phoneNumber).build()) .setPlatform(Common.Platform.GOOGLE) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -59,8 +58,7 @@ internal class PhoneVerificationApi @Inject constructor( val request = PhoneVerificationService.CheckVerificationCodeRequest.newBuilder() .setPhoneNumber(Common.PhoneNumber.newBuilder().setValue(request.phoneNumber).build()) .setCode(Model.VerificationCode.newBuilder().setValue(code).build()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -78,8 +76,7 @@ internal class PhoneVerificationApi @Inject constructor( ): PhoneVerificationService.UnlinkResponse { val request = PhoneVerificationService.UnlinkRequest.newBuilder() .setPhoneNumber(Common.PhoneNumber.newBuilder().setValue(request.phoneNumber).build()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -94,8 +91,7 @@ internal class PhoneVerificationApi @Inject constructor( ): PhoneVerificationService.LinkForPaymentResponse { val request = PhoneVerificationService.LinkForPaymentRequest.newBuilder() .setPhoneNumber(Common.PhoneNumber.newBuilder().setValue(request.phoneNumber).build()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ProfileApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ProfileApi.kt index f546aadfc1..704552c7d0 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ProfileApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ProfileApi.kt @@ -7,7 +7,7 @@ import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asFiatPaymentAmount import com.flipcash.services.internal.network.extensions.asUserId import com.flipcash.services.internal.network.extensions.asUsername -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.internal.network.extensions.linkingToken import com.flipcash.services.models.ProfileIdentifier import com.flipcash.services.models.SocialAccountLinkRequest @@ -48,8 +48,7 @@ internal class ProfileApi @Inject constructor( is ProfileIdentifier.Username -> setUsername(identifier.username.asUsername()) } } - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -67,8 +66,7 @@ internal class ProfileApi @Inject constructor( ): ProfileService.SetDisplayNameResponse { val request = ProfileService.SetDisplayNameRequest.newBuilder() .setDisplayName(displayName) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -86,8 +84,7 @@ internal class ProfileApi @Inject constructor( ): ProfileService.SetUsernameResponse { val request = ProfileService.SetUsernameRequest.newBuilder() .setUsername(username.asUsername()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -110,8 +107,7 @@ internal class ProfileApi @Inject constructor( com.codeinc.flipcash.gen.blob.v1.Model.BlobId.newBuilder() .setValue(blobId.bytes.toByteString()) ) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -129,8 +125,7 @@ internal class ProfileApi @Inject constructor( ): ProfileService.LinkSocialAccountResponse { val apiRequest = ProfileService.LinkSocialAccountRequest.newBuilder() .setLinkingToken(request.linkingToken()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } return withContext(Dispatchers.IO) { api.linkSocialAccount(apiRequest) @@ -150,8 +145,7 @@ internal class ProfileApi @Inject constructor( .setHex(hexColor) .build() ) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -170,8 +164,7 @@ internal class ProfileApi @Inject constructor( ): ProfileService.SetMinDmChatInitFeeResponse { val request = ProfileService.SetMinDmChatInitFeeRequest.newBuilder() .setMinDmChatInitFee(fee.asFiatPaymentAmount()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -193,7 +186,7 @@ internal class ProfileApi @Inject constructor( is SocialAccountUnlinkRequest.X -> builder.setXUserId(request.userId) } - val apiRequest = builder.apply { setAuth(authenticate(owner)) }.build() + val apiRequest = builder.buildAuthenticated(owner) { setAuth(it) } return withContext(Dispatchers.IO) { api.unlinkSocialAccount(apiRequest) diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PurchaseApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PurchaseApi.kt index e357917859..2afc46d484 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PurchaseApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PurchaseApi.kt @@ -7,7 +7,7 @@ import com.codeinc.flipcash.gen.iap.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.model.billing.IapMetadata import com.flipcash.services.internal.model.billing.Receipt -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi import dev.bmcreations.protovalidate.orThrow @@ -39,8 +39,7 @@ internal class PurchaseApi @Inject constructor( .setCurrency(metadata.currency.name.lowercase()) .setAmount(metadata.amount) ) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PushApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PushApi.kt index 3ee2494b5b..6e864db70c 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PushApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/PushApi.kt @@ -6,7 +6,7 @@ import com.codeinc.flipcash.gen.push.v1.PushGrpcKt import com.codeinc.flipcash.gen.push.v1.PushService import com.codeinc.flipcash.gen.push.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi import dev.bmcreations.protovalidate.orThrow @@ -40,8 +40,7 @@ internal class PushApi @Inject constructor( .setPushToken(token) .setAppInstall(Common.AppInstallId.newBuilder().setValue(installationId)) .setTokenType(Model.TokenType.FCM_ANDROID) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() @@ -60,8 +59,7 @@ internal class PushApi @Inject constructor( val request = PushService.DeleteTokensRequest.newBuilder() .setAppInstall(Common.AppInstallId.newBuilder().setValue(installationId)) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ReportingApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ReportingApi.kt index 64877e2a00..46e0c61d09 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ReportingApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ReportingApi.kt @@ -6,7 +6,7 @@ import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asChatId import com.flipcash.services.internal.network.extensions.asMessageId import com.flipcash.services.internal.network.extensions.asUserId -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.models.ReportTarget import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi @@ -35,7 +35,6 @@ internal class ReportingApi @Inject constructor( ): RpcReportingService.ReportResponse { val builder = RpcReportingService.ReportRequest.newBuilder() .setDescription(description) - .apply { setAuth(authenticate(owner)) } when (target) { is ReportTarget.User -> builder.setUserId(target.userId.asUserId()) @@ -51,7 +50,7 @@ internal class ReportingApi @Inject constructor( ) } - val request = builder.build() + val request = builder.buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() return withContext(Dispatchers.IO) { diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ResolverApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ResolverApi.kt index 36ee7fa64a..9aa330b2ba 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ResolverApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ResolverApi.kt @@ -6,7 +6,7 @@ import com.codeinc.flipcash.gen.resolver.v1.validate import com.flipcash.services.models.ResolveIdentifier import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asUserId -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.internal.network.extensions.asUsername import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.opencode.internal.network.core.GrpcApi @@ -34,8 +34,7 @@ internal class ResolverApi @Inject constructor( ): RpcResolverService.ResolveResponse { val request = RpcResolverService.ResolveRequest.newBuilder() .setIdentifier(identifier.asProtoIdentifier()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/SettingsApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/SettingsApi.kt index 13db7db1dc..9eeee9e2a6 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/SettingsApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/SettingsApi.kt @@ -5,7 +5,7 @@ import com.codeinc.flipcash.gen.settings.v1.SettingsGrpcKt import com.codeinc.flipcash.gen.settings.v1.SettingsService import com.codeinc.flipcash.gen.settings.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519 import com.getcode.opencode.internal.network.core.GrpcApi import dev.bmcreations.protovalidate.orThrow @@ -38,8 +38,7 @@ internal class SettingsApi @Inject constructor( setRegion(Common.Region.newBuilder().setValue(region.lowercase())) } } - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ThirdPartyApi.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ThirdPartyApi.kt index 157c7b8a8f..91c240ae28 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ThirdPartyApi.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ThirdPartyApi.kt @@ -5,7 +5,7 @@ import com.codeinc.flipcash.gen.thirdparty.v1.ThirdPartyService import com.codeinc.flipcash.gen.thirdparty.v1.validate import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.network.extensions.asApiKey -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.getcode.ed25519.Ed25519 import com.getcode.network.jwt.JwtSecuredEndpoint import com.getcode.opencode.internal.network.core.GrpcApi @@ -36,8 +36,7 @@ internal class ThirdPartyApi @Inject constructor( .setHost(endpoint.host) .setPath(endpoint.path) .setMethod(endpoint.method) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } request.validate().orThrow() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateMessage.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateMessage.kt index 4ac8d6eece..4f87ed3e64 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateMessage.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateMessage.kt @@ -5,6 +5,39 @@ import com.getcode.ed25519.Ed25519 import com.google.protobuf.GeneratedMessageLite import java.io.ByteArrayOutputStream +/** + * Builds the request with an [Common.Auth] that covers every field set on it. + * + * The signature is taken over the builder as it stands, and the server verifies it against the + * whole request minus the auth field. A field set after signing is therefore in what the server + * checks and missing from what the client signed, and the call comes back `UNAUTHENTICATED` — as + * GetBlobs did, where a late access context meant re-minting another user's avatar URL always + * failed. + * + * Ordering is the caller's problem only if the caller can get it wrong. This signs and builds in + * one step, so there is no builder left to set a field on. Prefer it to [authenticate]. + * + * @param attachAuth assigns the auth onto the request, almost always `{ setAuth(it) }`. It takes + * the builder as a receiver because the generated request types share no `setAuth` supertype. + */ +internal fun , B : GeneratedMessageLite.Builder> GeneratedMessageLite.Builder.buildAuthenticated( + owner: Ed25519.KeyPair, + attachAuth: B.(Common.Auth) -> Unit, +): M { + val auth = authenticate(owner) + + @Suppress("UNCHECKED_CAST") + (this as B).attachAuth(auth) + + return build()!! +} + +/** + * Signs the builder as it stands and returns the auth to assign onto it. + * + * Anything set after this call is outside the signature, which the type system does not enforce. + * Use [buildAuthenticated] instead unless the auth genuinely cannot be the last thing assigned. + */ internal fun , B : GeneratedMessageLite.Builder> GeneratedMessageLite.Builder.authenticate(owner: Ed25519.KeyPair): Common.Auth { // dump message up until this point into a ByteArray val bos = ByteArrayOutputStream() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/services/EventStreamingService.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/services/EventStreamingService.kt index 6147f3a773..0b7dac5abc 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/services/EventStreamingService.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/services/EventStreamingService.kt @@ -3,7 +3,7 @@ package com.flipcash.services.internal.network.services import com.codeinc.flipcash.gen.events.v1.EventStreamingService as RpcEventStreamingService import com.codeinc.flipcash.gen.events.v1.Model as EventModel import com.flipcash.services.internal.network.api.EventStreamingApi -import com.flipcash.services.internal.network.extensions.authenticate +import com.flipcash.services.internal.network.extensions.buildAuthenticated import com.flipcash.services.internal.network.extensions.toBlobUpdate import com.flipcash.services.internal.network.extensions.toChatUpdate import com.flipcash.services.models.StreamEventsError @@ -66,8 +66,7 @@ internal class EventStreamingService @Inject constructor( initialRequest = { val params = RpcEventStreamingService.StreamEventsRequest.Params.newBuilder() .setTs(currentTimestamp()) - .apply { setAuth(authenticate(owner)) } - .build() + .buildAuthenticated(owner) { setAuth(it) } RpcEventStreamingService.StreamEventsRequest.newBuilder() .setParams(params) diff --git a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateCallSiteTest.kt b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateCallSiteTest.kt new file mode 100644 index 0000000000..8fff803f3a --- /dev/null +++ b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/AuthenticateCallSiteTest.kt @@ -0,0 +1,54 @@ +package com.flipcash.services.internal.network.extensions + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +/** + * `authenticate` signs the builder as it stands, so a field assigned after it is outside the + * signature and the call comes back `UNAUTHENTICATED`. Nothing in the type system says so, and the + * mistake is invisible at a glance — it is a matter of which line a `setAuth` sits on. + * + * [buildAuthenticated] removes the choice by signing and building together. This keeps it that way: + * a new API that reaches for the lower-level `authenticate` has to justify itself here first. + */ +class AuthenticateCallSiteTest { + + /** + * `BlobStorageApi` hands `authenticate` to `getBlobsRequest`, which assigns it last under the + * cover of `GetBlobsRequestSigningTest`. + */ + private val allowed = setOf("BlobStorageApi.kt") + + @Test + fun `authenticate is only called where the ordering is already accounted for`() { + val callers = networkSources() + .filter { it.readText().contains("authenticate(") } + .map { it.name } + .sorted() + + assertEquals( + "Use buildAuthenticated so auth cannot be assigned before another field.", + allowed.sorted(), + callers, + ) + } + + private fun networkSources(): List { + val root = File("src/main/kotlin/com/flipcash/services/internal/network") + assertTrue( + "Expected to scan sources from the module directory, looked in ${root.absolutePath}", + root.isDirectory, + ) + + val sources = root.walkTopDown() + .filter { it.isFile && it.extension == "kt" } + .filterNot { it.name == "AuthenticateMessage.kt" } + .toList() + + // Guards against a silent pass if the scan ever stops finding the sources. + assertTrue("Scanned only ${sources.size} sources", sources.size > 20) + return sources + } +} diff --git a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/BuildAuthenticatedTest.kt b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/BuildAuthenticatedTest.kt new file mode 100644 index 0000000000..e7c754b490 --- /dev/null +++ b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/network/extensions/BuildAuthenticatedTest.kt @@ -0,0 +1,66 @@ +package com.flipcash.services.internal.network.extensions + +import com.codeinc.flipcash.gen.common.v1.Common +import com.getcode.ed25519.Ed25519 +import com.getcode.utils.toByteString +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import com.codeinc.flipcash.gen.chat.v1.ChatService as RpcChatService +import com.codeinc.flipcash.gen.messaging.v1.Model as MessagingModel + +/** + * The server verifies a request's auth against the request with auth cleared, so the signature has + * to be taken once every other field is set. [buildAuthenticated] signs and builds in one step to + * make that ordering impossible to get wrong; these tests hold it to that, and the second one + * executes the failure it prevents. + */ +class BuildAuthenticatedTest { + + private val owner: Ed25519.KeyPair = Ed25519.createKeyPair() + + private val chatId = Common.ChatId.newBuilder() + .setValue(ByteArray(32) { it.toByte() }.toByteString()) + .build() + + @Test + fun `auth covers every field set on the request`() { + val request = RpcChatService.GetChatRequest.newBuilder() + .setChatId(chatId) + .setViewMode(MessagingModel.ViewMode.REDACTED) + .buildAuthenticated(owner) { setAuth(it) } + + assertTrue(request.authCoversWholeRequest()) + } + + @Test + fun `a field set after the signature falls outside it`() { + // The hazard, written out: setViewMode lands after the bytes were signed, so the server + // verifies over a request the client never signed and rejects it as UNAUTHENTICATED. + val request = RpcChatService.GetChatRequest.newBuilder() + .setChatId(chatId) + .apply { setAuth(authenticate(owner)) } + .setViewMode(MessagingModel.ViewMode.REDACTED) + .build() + + assertFalse(request.authCoversWholeRequest()) + } + + @Test + fun `auth is not signed over itself`() { + val request = RpcChatService.GetChatRequest.newBuilder() + .setChatId(chatId) + .buildAuthenticated(owner) { setAuth(it) } + + assertTrue(request.hasAuth()) + assertFalse(request.toBuilder().clearAuth().build().hasAuth()) + assertTrue(request.authCoversWholeRequest()) + } + + /** Verifies the way the server does: over the request with its auth cleared. */ + private fun RpcChatService.GetChatRequest.authCoversWholeRequest(): Boolean = Ed25519.verify( + auth.keyPair.signature.value.toByteArray(), + toBuilder().clearAuth().build().toByteArray(), + auth.keyPair.pubKey.value.toByteArray(), + ) +}