mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Add a password strength estimator wrapping the SDK zxcvbn API (#7186)
* Add a password strength estimator wrapping the SDK zxcvbn API * Add tests for the password strength estimator SDK mapping --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.libraries.matrix.impl.encryption
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.encryption.PasswordStrengthEstimate
|
||||
import io.element.android.libraries.matrix.api.encryption.PasswordStrengthEstimator
|
||||
import io.element.android.libraries.matrix.api.encryption.PasswordStrengthRanking
|
||||
import org.matrix.rustcomponents.sdk.PasswordStrengthEstimate as SdkPasswordStrengthEstimate
|
||||
import org.matrix.rustcomponents.sdk.PasswordStrengthEstimator as SdkPasswordStrengthEstimator
|
||||
import org.matrix.rustcomponents.sdk.PasswordStrengthRanking as SdkPasswordStrengthRanking
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class RustPasswordStrengthEstimator : PasswordStrengthEstimator {
|
||||
// The SDK ranking is derived from caller-supplied thresholds. We use the "modern hardware (2025)"
|
||||
// preset so Android and iOS rank passphrases identically. The zxcvbn estimator holds no per-password
|
||||
// state, so a single app-scoped instance is reused for every keystroke rather than reconstructed.
|
||||
private val estimator by lazy { SdkPasswordStrengthEstimator.withModernDefaults2025() }
|
||||
|
||||
override fun estimate(password: String, userInputs: List<String>): PasswordStrengthEstimate {
|
||||
return estimator.estimate(password, userInputs).toApiModel()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SdkPasswordStrengthEstimate.toApiModel() = PasswordStrengthEstimate(
|
||||
ranking = ranking.toApiModel(),
|
||||
score = score,
|
||||
normalScore = normalScore,
|
||||
)
|
||||
|
||||
internal fun SdkPasswordStrengthRanking.toApiModel() = when (this) {
|
||||
SdkPasswordStrengthRanking.VERY_WEAK -> PasswordStrengthRanking.VeryWeak
|
||||
SdkPasswordStrengthRanking.WEAK -> PasswordStrengthRanking.Weak
|
||||
SdkPasswordStrengthRanking.FAIR -> PasswordStrengthRanking.Fair
|
||||
SdkPasswordStrengthRanking.STRONG -> PasswordStrengthRanking.Strong
|
||||
SdkPasswordStrengthRanking.VERY_STRONG -> PasswordStrengthRanking.VeryStrong
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.libraries.matrix.impl.encryption
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.encryption.PasswordStrengthRanking
|
||||
import io.element.android.libraries.matrix.test.encryption.FakePasswordStrengthEstimator
|
||||
import io.element.android.libraries.matrix.test.encryption.aPasswordStrengthEstimate
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.PasswordStrengthEstimate as SdkPasswordStrengthEstimate
|
||||
import org.matrix.rustcomponents.sdk.PasswordStrengthRanking as SdkPasswordStrengthRanking
|
||||
|
||||
class RustPasswordStrengthEstimatorTest {
|
||||
@Test
|
||||
fun `each SDK ranking maps to the matching api ranking`() {
|
||||
assertThat(SdkPasswordStrengthRanking.VERY_WEAK.toApiModel()).isEqualTo(PasswordStrengthRanking.VeryWeak)
|
||||
assertThat(SdkPasswordStrengthRanking.WEAK.toApiModel()).isEqualTo(PasswordStrengthRanking.Weak)
|
||||
assertThat(SdkPasswordStrengthRanking.FAIR.toApiModel()).isEqualTo(PasswordStrengthRanking.Fair)
|
||||
assertThat(SdkPasswordStrengthRanking.STRONG.toApiModel()).isEqualTo(PasswordStrengthRanking.Strong)
|
||||
assertThat(SdkPasswordStrengthRanking.VERY_STRONG.toApiModel()).isEqualTo(PasswordStrengthRanking.VeryStrong)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an SDK estimate forwards its ranking, score and normalScore to the api model`() {
|
||||
val sdkEstimate = SdkPasswordStrengthEstimate(
|
||||
ranking = SdkPasswordStrengthRanking.FAIR,
|
||||
guesses = 1_000_000uL,
|
||||
score = 6.0,
|
||||
normalScore = 0.35,
|
||||
feedback = null,
|
||||
)
|
||||
assertThat(sdkEstimate.toApiModel()).isEqualTo(
|
||||
aPasswordStrengthEstimate(
|
||||
ranking = PasswordStrengthRanking.Fair,
|
||||
score = 6.0,
|
||||
normalScore = 0.35,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the fake estimator returns its configured estimate`() {
|
||||
val estimate = aPasswordStrengthEstimate(ranking = PasswordStrengthRanking.Strong)
|
||||
val fake = FakePasswordStrengthEstimator { _, _ -> estimate }
|
||||
assertThat(fake.estimate("hunter2")).isEqualTo(estimate)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user