Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apollo: Release source code for 52 #152

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [52] - 2024-06-14

### ADDED

- Wallet delete
All users can now request wallet delete, to comply with Google Play Account Deletion Data Safety
policy.

## [51.11] - 2024-05-18

### FIXED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ abstract class Globals {
*/
abstract val rcLoginAuthorizePath: String

/**
* Get the path of this app's "Confirm Account Deletion" deeplink.
*/
abstract val confirmAccountDeletionPath: String

/**
* Get Lapp's URL.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import io.muun.apollo.data.os.GooglePlayServicesHelper;
import io.muun.apollo.data.os.HardwareCapabilitiesProvider;
import io.muun.apollo.data.os.OS_ExtensionsKt;
import io.muun.apollo.domain.errors.delete_wallet.NonEmptyWalletDeleteException;
import io.muun.apollo.domain.errors.delete_wallet.UnsettledOperationsWalletDeleteException;
import io.muun.apollo.domain.errors.newop.CyclicalSwapError;
import io.muun.apollo.domain.errors.newop.InvalidInvoiceException;
import io.muun.apollo.domain.errors.newop.InvoiceAlreadyUsedException;
Expand Down Expand Up @@ -42,6 +44,7 @@
import io.muun.apollo.domain.model.user.UserProfile;
import io.muun.common.Optional;
import io.muun.common.api.ChallengeSetupVerifyJson;
import io.muun.common.api.ChallengeSignatureJson;
import io.muun.common.api.CreateFirstSessionJson;
import io.muun.common.api.CreateLoginSessionJson;
import io.muun.common.api.CreateRcLoginSessionJson;
Expand Down Expand Up @@ -72,6 +75,7 @@
import io.muun.common.model.challenge.Challenge;
import io.muun.common.model.challenge.ChallengeSetup;
import io.muun.common.model.challenge.ChallengeSignature;
import io.muun.common.rx.CompletableFn;
import io.muun.common.rx.ObservableFn;
import io.muun.common.rx.RxHelper;
import io.muun.common.utils.Encodings;
Expand All @@ -80,6 +84,7 @@

import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import libwallet.MusigNonces;
import okhttp3.MediaType;
import rx.Completable;
Expand Down Expand Up @@ -346,7 +351,7 @@ public Observable<KeySet> loginCompatWithoutChallenge() {

/**
* Notify houston of a client logout. Not a critical request, in fact its just so Houston
* can now IN ADVANCE of a session expiration (otherwise will have to wait until a new create
* can know IN ADVANCE of a session expiration (otherwise will have to wait until a new create
* session to invalidate old ones). So, its a fire and forget call.
*/
public Observable<Void> notifyLogout(String authHeader) {
Expand Down Expand Up @@ -786,4 +791,29 @@ incomingSwap, new PreimageJson(Encodings.bytesToHex(preimage))
public Completable updateUserPreferences(final UserPreferences prefs) {
return getService().updateUserPreferences(prefs.toJson());
}

/**
* Irreversibly delete current user wallet.
*/
public Completable deleteWallet(@NonNull final ChallengeSignature challengeSignature) {
final ChallengeSignatureJson challengeSignatureJson =
apiMapper.mapChallengeSignature(challengeSignature);

return getService().deleteWallet(challengeSignatureJson)
.compose(CompletableFn.replaceHttpException(
ErrorCode.WALLET_NOT_EMPTY,
NonEmptyWalletDeleteException::new
))
.compose(CompletableFn.replaceHttpException(
ErrorCode.UNSETTLED_OPERATIONS,
UnsettledOperationsWalletDeleteException::new
));
}

/**
* Use an external confirmation link (received by email) to confirm account deletion.
*/
public Observable<Void> confirmAccountDeletion(String uuid) {
return getService().confirmAccountDeletion(new LinkActionJson(uuid));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.muun.apollo.domain.action.session.SyncApplicationDataAction;
import io.muun.apollo.domain.action.session.UseMuunLinkAction;
import io.muun.apollo.domain.action.session.rc_only.LogInWithRcAction;
import io.muun.apollo.domain.action.user.DeleteWalletAction;
import io.muun.apollo.domain.action.user.EmailLinkAction;
import io.muun.apollo.domain.action.user.UpdateProfilePictureAction;
import io.muun.apollo.domain.debug.DebugExecutable;
Expand Down Expand Up @@ -129,5 +130,7 @@ public interface ActionComponent {

UpdateContactsPermissionStateAction updateContactsPermissionStateAction();

DeleteWalletAction deleteWalletAction();

DebugExecutable debugExecutable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ open class UseMuunLinkAction @Inject constructor(
isEmailAuthorize(parser) -> houstonClient.useAuthorizeLink(uuid)
isEmailConfirm(parser) -> houstonClient.useConfirmLink(uuid)
isRcLoginAuthorize(parser) -> houstonClient.authorizeLoginWithRecoveryCode(uuid)
isAccountDeletionConfirm(parser) -> houstonClient.confirmAccountDeletion(uuid)

else ->
throw MissingCaseError(linkUri, "Muun links")
Expand All @@ -63,4 +64,7 @@ open class UseMuunLinkAction @Inject constructor(

private fun isRcLoginAuthorize(p: UriParser) =
p.pathWithSlash == Globals.INSTANCE.rcLoginAuthorizePath

private fun isAccountDeletionConfirm(p: UriParser) =
p.pathWithSlash == Globals.INSTANCE.confirmAccountDeletionPath
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.muun.apollo.domain.action.user

import io.muun.apollo.data.net.HoustonClient
import io.muun.apollo.domain.action.base.BaseAsyncAction0
import io.muun.apollo.domain.action.challenge_keys.SignChallengeAction
import io.muun.common.Optional
import io.muun.common.crypto.ChallengeType
import io.muun.common.model.challenge.Challenge
import rx.Observable
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class DeleteWalletAction @Inject constructor(
private val signChallenge: SignChallengeAction,
private val houstonClient: HoustonClient,
) : BaseAsyncAction0<Void>() {

override fun action(): Observable<Void> {
return Observable.defer {
houstonClient.requestChallenge(ChallengeType.USER_KEY)
.map { maybeChallenge: Optional<Challenge> ->
val challenge = maybeChallenge.orElseThrow() // empty only for legacy apps
signChallenge.signWithUserKey(challenge)
}
.flatMap { challengeSignature ->
houstonClient.deleteWallet(challengeSignature)
.andThen(Observable.just(null))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,16 @@ sealed class AnalyticsEvent(metadataKeyValues: List<Pair<String, Any>> = listOf(
class E_RECOVERY_CODE_SET_UP : AnalyticsEvent()
class E_LOG_OUT : AnalyticsEvent()
class E_WALLET_CREATED : AnalyticsEvent()
class E_WALLET_DELETED : AnalyticsEvent()
class E_WALLET_DELETE(val state: WalletDeleteState) : AnalyticsEvent(
listOf("type" to state.name.lowercase(Locale.getDefault()))
)

enum class WalletDeleteState {
STARTED,
ERROR,
SUCCESS,
}

class E_SIGN_IN_ABORTED : AnalyticsEvent()

class E_SIGN_IN_SUCCESSFUL(val type: LoginType) : AnalyticsEvent(
Expand Down Expand Up @@ -441,8 +450,7 @@ sealed class AnalyticsEvent(metadataKeyValues: List<Pair<String, Any>> = listOf(
RC_SETUP_START_CONNECTION_ERROR,
RC_SETUP_FINISH_CONNECTION_ERROR,
RC_STALE_ERROR,
RC_CREDENTIALS_DONT_MATCH_ERROR,
CRASHLYTICS
RC_CREDENTIALS_DONT_MATCH_ERROR
}

class E_ERROR(val type: ERROR_TYPE, vararg extras: Any) : AnalyticsEvent(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.muun.apollo.domain.errors.delete_wallet

import io.muun.apollo.domain.errors.MuunError

class NonEmptyWalletDeleteException(cause: Throwable) : MuunError(cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.muun.apollo.domain.errors.delete_wallet

import io.muun.apollo.domain.errors.MuunError

class UnsettledOperationsWalletDeleteException(cause: Throwable) : MuunError(cause)
7 changes: 5 additions & 2 deletions android/apolloui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,23 @@ static def configExternalLinks(productFlavor, String host) {
String authorizePath = "/link/authorize/index.html"
String changePasswdPath = "/link/confirm/index.html"
String rcLoginAuthPath = "/link/authorize-rc/index.html"
String confirmAccountDeletionPath = "/link/confirm-account-deletion/index.html"

// Required for AndroidManifest:
productFlavor.resValue "string", "muun_link_host", host
productFlavor.resValue "string", "verify_link_path", verifyPath
productFlavor.resValue "string", "authorize_link_path", authorizePath
productFlavor.resValue "string", "confirm_link_path", changePasswdPath
productFlavor.resValue "string", "rc_login_authorize_link_path", rcLoginAuthPath
productFlavor.resValue "string", "confirm_account_deletion_path", confirmAccountDeletionPath

// Required for code access in action layer:
productFlavor.buildConfigField "String", "MUUN_LINK_HOST", quote(host)
productFlavor.buildConfigField "String", "VERIFY_LINK_PATH", quote(verifyPath)
productFlavor.buildConfigField "String", "AUTHORIZE_LINK_PATH", quote(authorizePath)
productFlavor.buildConfigField "String", "CONFIRM_LINK_PATH", quote(changePasswdPath)
productFlavor.buildConfigField "String", "RC_LOGIN_AUTHORIZE_LINK_PATH", quote(rcLoginAuthPath)
productFlavor.buildConfigField "String", "CONFIRM_ACCOUNT_DELETION_PATH", quote(confirmAccountDeletionPath)
}


Expand All @@ -91,8 +94,8 @@ android {
applicationId "io.muun.apollo"
minSdkVersion 19
targetSdkVersion 33
versionCode 1111
versionName "51.11"
versionCode 1200
versionName "52"

// Needed to make sure these classes are available in the main DEX file for API 19
// See: https://spin.atomicobject.com/2018/07/16/support-kitkat-multidex/
Expand Down
10 changes: 10 additions & 0 deletions android/apolloui/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@
android:path="@string/rc_login_authorize_link_path" />
</intent-filter>

<intent-filter android:label="@string/confirm_link_handler_label">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https"
android:host="@string/muun_link_host"
android:path="@string/confirm_account_deletion_path" />
</intent-filter>

</activity>

<!-- SignupActivity -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class GlobalsImpl : Globals() {
override val rcLoginAuthorizePath: String
get() = BuildConfig.RC_LOGIN_AUTHORIZE_LINK_PATH

override val confirmAccountDeletionPath: String
get() = BuildConfig.CONFIRM_ACCOUNT_DELETION_PATH

override val lappUrl: String
get() = BuildConfig.LAPP_URL
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ class AlertDialogExtension @Inject constructor() : ActivityExtension() {
dismissDialog()

// ON dialog dismiss, dispose android's dialog reference to avoid memory leaks
dialog.addOnDismissAction {
activeDialog = null
dialog.addOnDismissAction { dismissedDialog ->
// If activeDialog has changed, it means dismissedDialog is already dismissed and a new
// dialog is being shown. We don't want to lose that ref (to properly dismiss it later).
if (activeDialog == dismissedDialog) {
activeDialog = null
}
}

activeDialog = dialog.show(activity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ class EmergencyKitSaveFragment : SingleFragment<EmergencyKitSavePresenter>(),
/** User's choice of application the last time the share dialog was displayed. */
private var chosenShareTarget: String? = null

/** Whether the loading dialog is currently on screen */
/** Whether the loading dialog is currently on screen. Required due to limitations of
* AlertDialogExtensions. It can't handle multiple dismissDialogs() calls prompted by
* handleStates(). */
private var showingLoadingDialog = false

override fun inject() =
Expand Down
Loading