Skip to content

Commit a77c7cb

Browse files
Simplify PasscodeManager API and decouple passcode/biometrics libraries (#73)
* Refactor biometric authentication logic into a generalized external authentication framework. * Rename all biometric-related references to "ExternalAuth" across `PasscodeManager`, `PasscodeScreen`, and associated components to support a broader range of authentication methods. * Introduce `BiometricStorageAdapter` interface and implementation to decouple biometric registration data from the core passcode module. * Deprecate registration-related methods in `PasscodeStorageAdapter` in favor of the new `BiometricStorageAdapter`. * Update `PasscodeManager` initialization and `PasscodeState` to use `isExternalAuthEnabled` instead of specific biometric flags. * Rename `PasscodeAction` and `PasscodeEvent` cases, such as `BiometricUnlockSuccess` to `ExternalUnlockSuccess` and `DisableBiometrics` to `DisableExternalAuth`. * Update `PasscodeScreen` and `PasscodeKeys` to use `externalAuthButton` as a generic slot for platform-specific authentication triggers. * Refactor navigation and setup logic in the sample app to integrate the new storage adapters and renamed callbacks. * Update string resource keys to reflect the transition from biometric-specific naming to external authentication. * Refactor `PasscodeManager` and `PasscodeScreen` to use a direct method API and a unified result callback. * **Passcode Logic and State Management**: * Refactor `PasscodeManager` to remove `CoroutineScope` and internal `Action`/`Event` channels, replacing them with direct method calls (e.g., `enterKey`, `changePasscode`, `logOut`). * Introduce `PasscodeResult` sealed interface to consolidate all operation outcomes (Verified, Created, Changed, etc.) into a single callback. * Add `shakeAnimationTrigger` to `PasscodeState` to handle UI feedback for rejected passcodes without relying on one-shot events. * Simplify `PasscodeManager` initialization by passing `isExternalAuthEnabled` directly to the constructor. * **UI Components**: * Update `PasscodeScreen` to use a single `onResult` lambda instead of multiple individual event callbacks. * Replace `LaunchedEffect` event collection with a `DisposableEffect` that registers the result callback. * Rename `biometricButton` to `externalAuthButton` in `PasscodeScreen` to be more agnostic of the authentication mechanism. * Update `PasscodeKeys`, `PasscodeLengthSwitch`, and `PasscodeForgotButton` to invoke manager methods directly. * **Integration and Navigation**: * Update `SampleAppNavigation` to map `PasscodeResult` values to navigation routes and storage updates. * Refactor `BiometricKey` and `BiometricSetupScreen` to interact with the updated `PasscodeManager` API, manually handling external auth status updates. * Update `BiometricStorageAdapter` implementation to separate biometric persistence from passcode persistence. * **Documentation and API**: * Update `README.md` for both passcode and biometrics modules to reflect the new API patterns and integration steps. * Add `UserCancelled` state to `RegistrationResult` and `AuthenticationResult` in the biometrics module. * Refine `PasscodeStorageAdapter` interface to focus exclusively on passcode data. * Refactor sample app navigation and modularize screen components. * Extract `HomeScreen`, `LoginScreen`, and `BiometricKey` into dedicated files within the `cmp-sample-shared` module to improve project structure. * Update `SampleAppNavigation` to handle consolidated `PasscodeResult` events, including verified, created, changed, and forgotten states. * Ensure consistent backstack management by using `popUpTo(0)` when navigating to top-level screens like `HomeScreen` and `LoginScreen`. * Update `PasscodeManager` to fully reset the internal state, including `isChangeFlow` and `isExternalAuthEnabled` flags, when clearing all security data. * Remove the redundant `SettingsPasscodeScreen` route. * Enhance `HomeScreen` with logic for toggling biometric authentication and triggering the passcode change flow. * Refactor `BiometricKey` to handle biometric authentication results and provide appropriate user feedback via callbacks.
1 parent fb256fe commit a77c7cb

19 files changed

Lines changed: 864 additions & 727 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-passcode-cmp/blob/development/LICENSE
9+
*/
10+
package cmp.sample.shared
11+
12+
import com.russhwolf.settings.Settings
13+
import org.mifos.authenticator.biometrics.BiometricStorageAdapter
14+
15+
private const val REGISTRATION_DATA_KEY = "org.mifos.authenticator.registration_data"
16+
17+
class BiometricStorageAdapterImpl(
18+
private val settings: Settings,
19+
) : BiometricStorageAdapter {
20+
override fun saveRegistrationData(registrationData: String) {
21+
settings.putString(REGISTRATION_DATA_KEY, registrationData)
22+
}
23+
24+
override fun loadRegistrationData(): String? {
25+
return settings.getStringOrNull(REGISTRATION_DATA_KEY)
26+
}
27+
28+
override fun deleteRegistrationData() {
29+
settings.remove(REGISTRATION_DATA_KEY)
30+
}
31+
}

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/PasscodeStorageAdapterImpl.kt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ package cmp.sample.shared
1212
import com.russhwolf.settings.Settings
1313
import org.mifos.authenticator.passcode.PasscodeStorageAdapter
1414

15-
const val PASSCODE_KEY = "org.mifos.authenticator.passcode"
16-
const val REGISTRATION_DATA_KEY = "org.mifos.authenticator.registration_data"
15+
private const val PASSCODE_KEY = "org.mifos.authenticator.passcode"
1716

1817
class PasscodeStorageAdapterImpl(
1918
private val settings: Settings,
@@ -30,15 +29,12 @@ class PasscodeStorageAdapterImpl(
3029
settings.remove(PASSCODE_KEY)
3130
}
3231

33-
override fun saveRegistrationData(registrationData: String) {
34-
settings.putString(REGISTRATION_DATA_KEY, registrationData)
35-
}
32+
@Suppress("DEPRECATION")
33+
override fun saveRegistrationData(registrationData: String) { }
3634

37-
override fun loadRegistrationData(): String? {
38-
return settings.getStringOrNull(REGISTRATION_DATA_KEY)
39-
}
35+
@Suppress("DEPRECATION")
36+
override fun loadRegistrationData(): String? = null
4037

41-
override fun deleteRegistrationData() {
42-
settings.remove(REGISTRATION_DATA_KEY)
43-
}
38+
@Suppress("DEPRECATION")
39+
override fun deleteRegistrationData() { }
4440
}

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/di/PasscodeModule.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,25 @@
99
*/
1010
package cmp.sample.shared.di
1111

12+
import cmp.sample.shared.BiometricStorageAdapterImpl
1213
import cmp.sample.shared.PasscodeStorageAdapterImpl
1314
import com.russhwolf.settings.Settings
14-
import kotlinx.coroutines.MainScope
1515
import org.koin.core.context.startKoin
1616
import org.koin.core.module.dsl.singleOf
1717
import org.koin.dsl.KoinAppDeclaration
1818
import org.koin.dsl.bind
1919
import org.koin.dsl.module
20+
import org.mifos.authenticator.biometrics.BiometricStorageAdapter
2021
import org.mifos.authenticator.passcode.PasscodeManager
2122
import org.mifos.authenticator.passcode.PasscodeStorageAdapter
2223

2324
val passcodeModule = module {
2425
singleOf(::PasscodeStorageAdapterImpl).bind<PasscodeStorageAdapter>()
26+
singleOf(::BiometricStorageAdapterImpl).bind<BiometricStorageAdapter>()
2527
single {
26-
PasscodeManager(get(), MainScope()).initialize()
28+
val biometricAdapter = get<BiometricStorageAdapter>()
29+
val isExternalAuthEnabled = biometricAdapter.loadRegistrationData() != null
30+
PasscodeManager(get(), isExternalAuthEnabled)
2731
}
2832
single { Settings() }
2933
}

cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/navigation/Route.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,4 @@ sealed class Route {
2323

2424
@Serializable
2525
data object BiometricSetupScreen : Route()
26-
27-
@Serializable
28-
data object SettingsPasscodeScreen : Route()
2926
}

0 commit comments

Comments
 (0)