Skip to content

Releases: openMF/mifos-passcode-cmp

2.2.0

19 Apr 12:57
2bb1f3f

Choose a tag to compare

What's Changed


Full decoupling of the passcode and biometrics libraries. Neither library imports the other; PasscodeManager has no knowledge of external authenticators, and the biometrics library owns the registration-data lifecycle internally.

Breaking Changes

Passcode Library — fully biometrics-agnostic:

  • PasscodeManager methods removed: disableExternalAuth(), notifyExternalAuthSuccess(), setExternalAuthEnabled().
  • PasscodeManager constructor now takes only adapter — the isExternalAuthEnabled: Boolean parameter is gone.
  • PasscodeState.isExternalAuthEnabled field removed.
  • PasscodeResult.ExternalAuthDisabled case removed.
  • PasscodeStep.DisableExternalAuth case removed.
  • PasscodeStorageAdapter: the three deprecated methods saveRegistrationData(), loadRegistrationData(), deleteRegistrationData() are removed entirely — use BiometricStorageAdapter from the biometrics library instead.
  • PasscodeScreen now accepts isExternalAuthEnabled: Boolean as a direct parameter (caller-controlled) instead of reading it from PasscodeState.

Biometrics Library — owns registration-data lifecycle:

  • PlatformAuthenticationProvider constructor now requires PlatformAuthenticator + BiometricStorageAdapter (previously took an optional activity: Any?).
  • PlatformAuthenticationProvider.onAuthenticatorClick() no longer accepts savedRegistrationData — the stored blob is loaded internally from the adapter.
  • Composition local renamed: PlatformAuthenticatorLocalCompositionProviderPlatformAuthenticatorCompositionProvider. It now requires a BiometricStorageAdapter parameter.

New Features

  • Automatic registration-data persistence. registerUser() saves on success; onAuthenticatorClick() loads on demand; new unregister() clears. Consumers no longer call BiometricStorageAdapter.saveRegistrationData / loadRegistrationData / deleteRegistrationData directly.
  • isRegistered: StateFlow<Boolean> on PlatformAuthenticationProvider. Observe registration state reactively and drive UI from it.
  • unregister() suspend function on PlatformAuthenticationProvider — clears the stored blob and flips isRegistered to false.

Fixes

  • Windows/WebAuthn decode bug. onAuthenticatorClick() previously fell back to an empty string when no registration blob was stored, which could trigger a WebAuthn decode error on Windows/Desktop and misreport as UserNotRegistered. It now short-circuits cleanly to AuthenticationResult.UserNotRegistered when no blob exists. Android behaviour is unchanged — Android's empty-string-on-success marker still passes through correctly.

Migration

Passcode library:

Before:

// DI
PasscodeManager(
    adapter = get(),
    isExternalAuthEnabled = biometricAdapter.loadRegistrationData() != null,
)

// Biometric unlock — went through the manager
passcodeManager.notifyExternalAuthSuccess()

// Enable / disable external auth
passcodeManager.setExternalAuthEnabled(true)
passcodeManager.disableExternalAuth()

// Screen
PasscodeScreen(
    passcodeManager = passcodeManager,
    onResult = { result ->
        when (result) {
            PasscodeResult.ExternalAuthDisabled -> popBack()
            // other cases
        }
    },
)

After:

// DI — no external-auth flag at construction
PasscodeManager(adapter = get())

// Biometric unlock — handle in your app, never routed through PasscodeManager
// (use a separate callback from your biometric button; the passcode library no
// longer has a "mark verified from outside" API)

// Enable / disable — the biometrics library owns this state
authProvider.registerUser(...)   // sets isRegistered = true automatically
authProvider.unregister()        // sets isRegistered = false automatically

// Screen — pass isExternalAuthEnabled explicitly, drive from whatever state you own
val isRegistered by authProvider.isRegistered.collectAsState()

PasscodeScreen(
    passcodeManager = passcodeManager,
    onResult = { result ->
        when (result) {
            PasscodeResult.Verified -> ...
            PasscodeResult.Created -> ...
            PasscodeResult.Changed -> ...
            PasscodeResult.Forgotten -> ...
            PasscodeResult.Rejected -> ...
            // ExternalAuthDisabled no longer exists
        }
    },
    isExternalAuthEnabled = isRegistered,
    externalAuthButton = { modifier -> YourBiometricButton(modifier) },
)

Biometrics library:

Before:

@Composable
fun App() {
    PlatformAuthenticatorLocalCompositionProvider {
        MaterialTheme { /* ... */ }
    }
}

// Register — consumer saves the blob
val result = authProvider.registerUser(...)
if (result is RegistrationResult.Success) {
    biometricStorageAdapter.saveRegistrationData(result.message)
}

// Authenticate — consumer loads the blob
val blob = biometricStorageAdapter.loadRegistrationData() ?: ""
val result = authProvider.onAuthenticatorClick(appName, blob)

// Unregister — consumer deletes the blob
biometricStorageAdapter.deleteRegistrationData()

After:

@Composable
fun App() {
    val biometricStorageAdapter = koinInject<BiometricStorageAdapter>()
    PlatformAuthenticatorCompositionProvider(biometricStorageAdapter) {
        MaterialTheme { /* ... */ }
    }
}

// Register — save happens internally
authProvider.registerUser(...)

// Authenticate — load happens internally; no registration-data argument
val result = authProvider.onAuthenticatorClick(appName)

// Unregister — clears storage and flips isRegistered
authProvider.unregister()

Notes

  • Bridging the two libraries is now the consumer's responsibility — typically via a small wrapper composable that passes the biometrics library's isRegistered state into PasscodeScreen and renders the biometric button. See the sample app (cmp-sample-shared) for a working reference: PasscodeScreenWithBiometrics.kt.
  • Both library READMEs have been rewritten to describe only their own library; cross-library integration examples live in the sample app.

2.1.0

04 Apr 17:44
a77c7cb

Choose a tag to compare

What's Changed

Full Changelog: 2.0.5...breaking-changes

Breaking Changes

PasscodeManager API overhaul:

  • Constructor no longer takes CoroutineScope. Now takes isExternalAuthEnabled: Boolean parameter.
  • PasscodeAction sealed interface removed. Use direct methods instead:
    • passcodeManager.changePasscode()
    • passcodeManager.logOut()
    • passcodeManager.disableExternalAuth()
    • passcodeManager.notifyExternalAuthSuccess()
    • passcodeManager.setExternalAuthEnabled(enabled)
  • PasscodeEvent sealed interface removed. Use PasscodeResult via onResult callback.
  • trySendAction() removed.
  • rememberPasscodeManager() removed. Scope as a singleton via your DI framework.
  • .initialize() removed. Initialization happens in constructor.

PasscodeScreen signature changed:

  • 7 individual callbacks (onPasscodeConfirm, onForgotButton, onPasscodeCreation, onPasscodeChanged, onPasscodeRejected, onDisableExternalAuth, onExternalAuthError) replaced by single onResult: (PasscodeResult) -> Unit.
  • biometricButton parameter renamed to externalAuthButton.

PasscodeStorageAdapter:

  • saveRegistrationData(), loadRegistrationData(), deleteRegistrationData() are deprecated. Use BiometricStorageAdapter from the biometrics library instead.

Naming:

  • All Biometric* names in PasscodeAction, PasscodeEvent, PasscodeStep, PasscodeState renamed to External* equivalents (e.g. isBiometricEnabled -> isExternalAuthEnabled, DisableBiometrics -> DisableExternalAuth).

Migration

Before:

// Constructor
PasscodeManager(adapter, MainScope()).initialize(isExternalAuthEnabled)

// Sending actions
passcodeManager.trySendAction(PasscodeAction.ChangePasscode)
passcodeManager.trySendAction(PasscodeAction.BiometricUnlockSuccess)

// PasscodeScreen
PasscodeScreen(
    passcodeManager = passcodeManager,
    onPasscodeConfirm = { navigate(Home) },
    onPasscodeCreation = { navigate(Setup) },
    onForgotButton = { navigate(Login) },
    // ... 4 more callbacks
)

After:

// Constructor
PasscodeManager(adapter, isExternalAuthEnabled)

// Direct methods
passcodeManager.changePasscode()
passcodeManager.notifyExternalAuthSuccess()

// PasscodeScreen
PasscodeScreen(
    passcodeManager = passcodeManager,
    onResult = { result ->
        when (result) {
            PasscodeResult.Verified -> navigate(Home)
            PasscodeResult.Created -> navigate(Setup)
            PasscodeResult.Forgotten -> navigate(Login)
            PasscodeResult.Changed -> navigate(Home)
            PasscodeResult.ExternalAuthDisabled -> popBack()
            PasscodeResult.Rejected -> { }
        }
    },
)

Biometrics Library:

  • AuthenticationResult.UserCancelled added — when blocks on AuthenticationResult must handle this new variant.
  • RegistrationResult.UserCancelled added — when blocks on RegistrationResult must handle this new variant.

passcode-2.0.5, biometrics-2.0.4

19 Feb 10:52
e6f2917

Choose a tag to compare

What's Changed

Full Changelog: passcode-v2.0.4...2.0.5

passcode-2.0.4

25 Feb 06:24
b1c3239

Choose a tag to compare

What's Changed

Full Changelog: 2.0.3...passcode-v2.0.4

2.0.3

23 Feb 08:30
6e118cc

Choose a tag to compare

What's Changed

Full Changelog: Release...2.0.3

2.0.0

14 Feb 16:17
29ac50c

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: 1.0.5...2.00

1.0.5: Merge pull request #4 from openMF/feat_sample

06 Apr 17:52
aad9743

Choose a tag to compare

Feature sample compose multiplatform project

1.0.4

30 Mar 20:24

Choose a tag to compare

Updated publish.yml