diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt index c0f7833e23..08dc768b6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameRepository.kt @@ -131,12 +131,7 @@ object UsernameRepository { } /** - * Creates or rotates the username link for the local user. If successful, the [UsernameLinkComponents] will be returned. - * If it fails for any reason, the optional will be empty. - * - * The assumption here is that when the user clicks this button, they will either have a new link, or no link at all. - * This is to prevent indeterminate states where the network call fails but may have actually succeeded, that kind of thing. - * As such, it's recommended to block calling this method on a network check. + * Creates or rotates the username link for the local user. */ fun createOrResetUsernameLink(): Single { if (!NetworkUtil.isConnected(ApplicationDependencies.getApplication())) { @@ -376,9 +371,10 @@ object UsernameRepository { Log.i(TAG, "[confirmUsernameAndCreateNewLink] Beginning username confirmation...") return try { - accountManager.confirmUsername(username) + val linkComponents: UsernameLinkComponents = accountManager.confirmUsernameAndCreateNewLink(username) + SignalStore.account().username = username.username - SignalStore.account().usernameLink = null + SignalStore.account().usernameLink = linkComponents SignalDatabase.recipients.setUsername(Recipient.self().id, username.username) SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC SignalStore.account().usernameSyncErrorCount = 0 @@ -387,12 +383,6 @@ object UsernameRepository { StorageSyncHelper.scheduleSyncForDataChange() Log.i(TAG, "[confirmUsernameAndCreateNewLink] Successfully confirmed username.") - if (tryToSetUsernameLink(username)) { - Log.i(TAG, "[confirmUsernameAndCreateNewLink] Successfully confirmed username link.") - } else { - Log.w(TAG, "[confirmUsernameAndCreateNewLink] Failed to confirm a username link. We'll try again when the user goes to view their link.") - } - UsernameSetResult.SUCCESS } catch (e: UsernameTakenException) { Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username gone.") @@ -409,23 +399,6 @@ object UsernameRepository { } } - private fun tryToSetUsernameLink(username: Username): Boolean { - for (i in 0..2) { - try { - val linkComponents = accountManager.createUsernameLink(username) - SignalStore.account().usernameLink = linkComponents - - SignalDatabase.recipients.markNeedsSync(Recipient.self().id) - StorageSyncHelper.scheduleSyncForDataChange() - return true - } catch (e: IOException) { - Log.w(TAG, "[tryToSetUsernameLink] Failed with IOException on attempt " + (i + 1) + "/3", e) - } - } - - return false - } - @WorkerThread private fun deleteUsernameInternal(): UsernameDeleteResult { return try { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 7bd4c48f3b..a499489dfb 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -783,8 +783,15 @@ public class SignalServiceAccountManager { return this.pushServiceSocket.reserveUsername(usernameHashes); } - public void confirmUsername(Username username) throws IOException { - this.pushServiceSocket.confirmUsername(username); + public UsernameLinkComponents confirmUsernameAndCreateNewLink(Username username) throws IOException { + try { + UsernameLink link = link = username.generateLink(); + UUID serverId = this.pushServiceSocket.confirmUsernameAndCreateNewLink(username, link); + + return new UsernameLinkComponents(link.getEntropy(), serverId); + } catch (BaseUsernameException e) { + throw new AssertionError(e); + } } public UsernameLinkComponents updateUsernameLink(UsernameLink newUsernameLink) throws IOException { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java index cd617ace4b..162ce6b6af 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java @@ -9,8 +9,12 @@ class ConfirmUsernameRequest { @JsonProperty private String zkProof; - ConfirmUsernameRequest(String usernameHash, String zkProof) { - this.usernameHash = usernameHash; - this.zkProof = zkProof; + @JsonProperty + private String encryptedUsername; + + ConfirmUsernameRequest(String usernameHash, String zkProof, String encryptedUsername) { + this.usernameHash = usernameHash; + this.zkProof = zkProof; + this.encryptedUsername = encryptedUsername; } } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameResponse.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameResponse.kt new file mode 100644 index 0000000000..49a8edd8f0 --- /dev/null +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameResponse.kt @@ -0,0 +1,16 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import org.whispersystems.signalservice.internal.util.JsonUtil +import java.util.UUID + +/** Response body for confirming a username reservation. */ +class ConfirmUsernameResponse( + @JsonProperty + val usernameHash: String, + + @JsonProperty + @JsonDeserialize(using = JsonUtil.UuidDeserializer::class) + val usernameLinkHandle: UUID +) diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index fb189e9d23..204b7503a3 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -1112,15 +1112,19 @@ public class PushServiceSocket { * @param username The username the user wishes to confirm. * @throws IOException Thrown when the username is invalid or taken, or when another network error occurs. */ - public void confirmUsername(Username username) throws IOException { + public UUID confirmUsernameAndCreateNewLink(Username username, Username.UsernameLink link) throws IOException { try { byte[] randomness = new byte[32]; random.nextBytes(randomness); byte[] proof = username.generateProofWithRandomness(randomness); - ConfirmUsernameRequest confirmUsernameRequest = new ConfirmUsernameRequest(Base64.encodeUrlSafeWithoutPadding(username.getHash()), Base64.encodeUrlSafeWithoutPadding(proof)); + ConfirmUsernameRequest confirmUsernameRequest = new ConfirmUsernameRequest( + Base64.encodeUrlSafeWithoutPadding(username.getHash()), + Base64.encodeUrlSafeWithoutPadding(proof), + Base64.encodeUrlSafeWithoutPadding(link.getEncryptedUsername()) + ); - makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body) -> { + String response = makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body) -> { switch (responseCode) { case 409: throw new UsernameIsNotReservedException(); @@ -1128,6 +1132,8 @@ public class PushServiceSocket { throw new UsernameTakenException(); } }, Optional.empty()); + + return JsonUtil.fromJson(response, ConfirmUsernameResponse.class).getUsernameLinkHandle(); } catch (BaseUsernameException e) { throw new IOException(e); }