Add enterprise gate for custom recovery passphrase

Add isCustomRecoveryPassphraseEnabled() and
estimateCustomRecoveryPassphraseStrength() to EnterpriseService, plus the
CustomRecoveryPassphraseStrength result types. FOSS builds return
false/null so the feature stays gated off; the enterprise implementation
(estimator) lands separately in the enterprise repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jenna Vassar
2026-06-02 13:51:59 -07:00
parent 2931418018
commit 62ac6a3d2f
4 changed files with 64 additions and 0 deletions
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.enterprise.api
/**
* Strength tier surfaced to the user under a custom recovery passphrase field.
* The eight tiers mirror iOS `PasswordStrengthThreshold` 1:1 so both clients label a given
* passphrase identically; each maps to its own label and time-to-crack description in the UI.
*/
enum class CustomRecoveryPassphraseStrength {
Garbage,
Weak,
Moderate,
Okay,
Strong,
VeryStrong,
UltraStrong,
Mega,
}
/**
* Result of estimating a custom recovery passphrase's strength. The estimation algorithm itself
* is an enterprise capability ([EnterpriseService.estimateCustomRecoveryPassphraseStrength]); FOSS
* builds never produce one.
*/
data class CustomRecoveryPassphraseStrengthResult(
val strength: CustomRecoveryPassphraseStrength,
/** Fill amount for the progress bar. Always in `0f..1f`. */
val score: Float,
)
@@ -41,6 +41,22 @@ interface EnterpriseService {
*/
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
/**
* Whether the user is allowed to set a custom recovery passphrase.
* Enterprise builds gate this in addition to the homeserver's .well-known `customRecoveryPassphraseRequirements`;
* when false the setup flow falls back to the auto-generated key path even if the homeserver
* advertises a spec.
*/
fun isCustomRecoveryPassphraseEnabled(): Boolean
/**
* Estimate the strength of a user-typed custom recovery [passphrase].
* The estimation algorithm is an enterprise capability: FOSS builds return null, so callers
* should treat null as "no indicator to show". Callers are responsible for not calling this with
* an empty passphrase if they want the indicator hidden while the field is empty.
*/
fun estimateCustomRecoveryPassphraseStrength(passphrase: String): CustomRecoveryPassphraseStrengthResult?
companion object {
const val ANY_ACCOUNT_PROVIDER = "*"
}
@@ -13,6 +13,7 @@ import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.compound.colors.SemanticColorsLightDark
import io.element.android.features.enterprise.api.BugReportUrl
import io.element.android.features.enterprise.api.CustomRecoveryPassphraseStrengthResult
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.coroutines.flow.Flow
@@ -45,4 +46,8 @@ class DefaultEnterpriseService : EnterpriseService {
}
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
override fun isCustomRecoveryPassphraseEnabled(): Boolean = false
override fun estimateCustomRecoveryPassphraseStrength(passphrase: String): CustomRecoveryPassphraseStrengthResult? = null
}
@@ -11,6 +11,7 @@ package io.element.android.features.enterprise.test
import androidx.compose.ui.graphics.Color
import io.element.android.compound.colors.SemanticColorsLightDark
import io.element.android.features.enterprise.api.BugReportUrl
import io.element.android.features.enterprise.api.CustomRecoveryPassphraseStrengthResult
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.tests.testutils.lambda.lambdaError
@@ -31,6 +32,8 @@ class FakeEnterpriseService(
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
private val tweakMasUrlResult: (String, String) -> String = { _, _ -> lambdaError() },
private val isCustomRecoveryPassphraseEnabledResult: Boolean = false,
private val estimateCustomRecoveryPassphraseStrengthResult: (String) -> CustomRecoveryPassphraseStrengthResult? = { null },
) : EnterpriseService {
private val brandColorState = MutableStateFlow(initialBrandColor)
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
@@ -79,4 +82,9 @@ class FakeEnterpriseService(
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? {
return getNoisyNotificationChannelIdResult(sessionId)
}
override fun isCustomRecoveryPassphraseEnabled(): Boolean = isCustomRecoveryPassphraseEnabledResult
override fun estimateCustomRecoveryPassphraseStrength(passphrase: String): CustomRecoveryPassphraseStrengthResult? =
estimateCustomRecoveryPassphraseStrengthResult(passphrase)
}