mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Allow enableRecovery to derive the key from a passphrase
enableRecovery now takes an optional passphrase and returns the recovery key as the suspend result (like resetRecoveryKey), avoiding the flow/return-value race. For the passphrase path the SDK-derived base58 key is kept out of the session-scoped progress flow so it is never surfaced to the user; the caller receives it as the return value and is responsible for scrubbing it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-2
@@ -24,9 +24,15 @@ interface EncryptionService {
|
|||||||
suspend fun enableBackups(): Result<Unit>
|
suspend fun enableBackups(): Result<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable recovery. Observe enableProgressStateFlow to get progress and recovery key.
|
* Enable recovery and return the SDK-generated recovery key on success.
|
||||||
|
* Observe [enableRecoveryProgressStateFlow] for in-progress UI updates.
|
||||||
|
*
|
||||||
|
* @param waitForBackupsToUpload when true, suspends until existing room keys finish uploading.
|
||||||
|
* @param passphrase optional user-supplied passphrase. When set, the SDK derives the
|
||||||
|
* secret-storage key from it instead of a random base58 key; the passphrase can later be
|
||||||
|
* passed to [recover], and the returned base58 key should not be surfaced to the user.
|
||||||
*/
|
*/
|
||||||
suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit>
|
suspend fun enableRecovery(waitForBackupsToUpload: Boolean, passphrase: String? = null): Result<String>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the recovery and return the new recovery key.
|
* Change the recovery and return the new recovery key.
|
||||||
|
|||||||
+14
-5
@@ -131,19 +131,28 @@ class RustEncryptionService(
|
|||||||
|
|
||||||
override suspend fun enableRecovery(
|
override suspend fun enableRecovery(
|
||||||
waitForBackupsToUpload: Boolean,
|
waitForBackupsToUpload: Boolean,
|
||||||
): Result<Unit> = withContext(dispatchers.io) {
|
passphrase: String?,
|
||||||
|
): Result<String> = withContext(dispatchers.io) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
service.enableRecovery(
|
// The key arrives as the suspend return value (like resetRecoveryKey), avoiding a
|
||||||
|
// flow/return-value race; the listener only feeds sub-progress.
|
||||||
|
enableRecoveryProgressStateFlow.value = EnableRecoveryProgress.Starting
|
||||||
|
val key = service.enableRecovery(
|
||||||
waitForBackupsToUpload = waitForBackupsToUpload,
|
waitForBackupsToUpload = waitForBackupsToUpload,
|
||||||
progressListener = object : EnableRecoveryProgressListener {
|
progressListener = object : EnableRecoveryProgressListener {
|
||||||
override fun onUpdate(status: RustEnableRecoveryProgress) {
|
override fun onUpdate(status: RustEnableRecoveryProgress) {
|
||||||
enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status)
|
enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
passphrase = null,
|
passphrase = passphrase,
|
||||||
)
|
)
|
||||||
// enableRecovery returns the encryption key, but we read it from the state flow
|
// Pin Done explicitly so observers get a coherent terminal value. For the passphrase
|
||||||
.let { }
|
// path the user never sees the SDK base58 key, so keep it out of this session-scoped
|
||||||
|
// (in-memory) flow entirely; the caller still receives it as the return value and is
|
||||||
|
// responsible for scrubbing it. The auto-generated path must retain the key here since
|
||||||
|
// that is how it is surfaced to the user.
|
||||||
|
enableRecoveryProgressStateFlow.value = EnableRecoveryProgress.Done(if (passphrase != null) "" else key)
|
||||||
|
key
|
||||||
}.mapFailure {
|
}.mapFailure {
|
||||||
it.mapRecoveryException()
|
it.mapRecoveryException()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -28,7 +28,8 @@ class FakeEncryptionService(
|
|||||||
private val pinUserIdentityResult: (UserId) -> Result<Unit> = { lambdaError() },
|
private val pinUserIdentityResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||||
private val withdrawVerificationResult: (UserId) -> Result<Unit> = { lambdaError() },
|
private val withdrawVerificationResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||||
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() },
|
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() },
|
||||||
private val enableRecoveryLambda: (Boolean) -> Result<Unit> = { lambdaError() },
|
private val enableRecoveryLambda: (Boolean, String?) -> Result<String> = { _, _ -> lambdaError() },
|
||||||
|
private val resetRecoveryKeyLambda: () -> Result<String> = { Result.success(FAKE_RECOVERY_KEY) },
|
||||||
) : EncryptionService {
|
) : EncryptionService {
|
||||||
private var disableRecoveryFailure: Exception? = null
|
private var disableRecoveryFailure: Exception? = null
|
||||||
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
||||||
@@ -90,11 +91,11 @@ class FakeEncryptionService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
|
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
|
||||||
return Result.success(FAKE_RECOVERY_KEY)
|
return resetRecoveryKeyLambda()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit> = simulateLongTask {
|
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean, passphrase: String?): Result<String> = simulateLongTask {
|
||||||
return enableRecoveryLambda(waitForBackupsToUpload)
|
return enableRecoveryLambda(waitForBackupsToUpload, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
|
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user