-
Notifications
You must be signed in to change notification settings - Fork 10
feat(settings): module-owned settings screen with change password, biometrics, and autofill #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
6b5bc00
feat(settings): add group-entry DSL
OffRange 3109fef
feat(app): wire SettingsScreen into the settings nav destination
OffRange 58cafc1
fix(auth): add serialization plugin again
OffRange 4248f2c
feat(autofill): add autofill service repo
OffRange 0abcb37
build: api(projects.core.security)
OffRange f121b7f
feat(settings): implement autofill switch
OffRange 05c5570
feat(core-identity): add account observable function
OffRange 919bf6d
feat(settings): add biometric enrollment
OffRange a725ea6
fix(autofill): disable autofill
OffRange c1abdef
fix(settings): add 3rd party section
OffRange d9a85a0
fix(settings): add version section
OffRange b59b2f4
feat(settings): add report issues
OffRange b860f3c
feat(libraries): implement 3rd party licenses
OffRange 61a6b50
feat(identity): add ChangePasswordUseCase
OffRange 63fe611
fix(identity): scrub ARK without leaving a copy; make FakeKeyWrapper …
OffRange db54bf5
feat(settings): add ChangePasswordViewModel
OffRange 435b511
feat(settings): add ChangePasswordScreen
OffRange cea457e
feat(settings): add module-owned settings graph with change-password …
OffRange 3511ad4
feat(app): back Settings tab with module-owned settings graph
OffRange 64fee37
feat(settings): biometric reauth for change password (#59)
OffRange 401873b
feat(settings): add descriptions and refactor navigation icons
OffRange 4db0e65
feat(settings): add export and import data actions
OffRange 29b259c
fix(identity): zeroize transient key material during password change
OffRange ef2586c
fix(settings): correct autofill toggle routing and gate biometrics row
OffRange 310cd3e
test(settings): provide ViewModel fakes via testFixtures
OffRange cbfd93b
fix(settings): handle async autofill disable and update biometric def…
OffRange 14ae1e7
fix(settings): animate entries
OffRange File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
...ity/src/main/kotlin/de/davis/keygo/core/identity/domain/model/BiometricEnrollmentError.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package de.davis.keygo.core.identity.domain.model | ||
|
|
||
| import de.davis.keygo.core.security.domain.model.BiometricAuthError | ||
|
|
||
| sealed interface BiometricEnrollmentError { | ||
| data object NoActiveAccount : BiometricEnrollmentError | ||
| data object WrappingFailed : BiometricEnrollmentError | ||
| data object PersistenceFailed : BiometricEnrollmentError | ||
| data class BiometricFailed(val error: BiometricAuthError) : BiometricEnrollmentError | ||
| } |
11 changes: 11 additions & 0 deletions
11
...identity/src/main/kotlin/de/davis/keygo/core/identity/domain/model/ChangePasswordError.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package de.davis.keygo.core.identity.domain.model | ||
|
|
||
| sealed interface ChangePasswordError { | ||
|
|
||
| data object ActiveAccountNotFound : ChangePasswordError | ||
| data object IncorrectPassword : ChangePasswordError | ||
| data object BiometricNotEnrolled : ChangePasswordError | ||
| data object KeyDerivationFailed : ChangePasswordError | ||
| data object WrappingFailed : ChangePasswordError | ||
| data object PersistenceFailed : ChangePasswordError | ||
| } |
8 changes: 8 additions & 0 deletions
8
core/identity/src/main/kotlin/de/davis/keygo/core/identity/domain/model/Reauthentication.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package de.davis.keygo.core.identity.domain.model | ||
|
|
||
| sealed interface Reauthentication { | ||
|
|
||
| data class Password(val currentPassword: String) : Reauthentication | ||
|
|
||
| class Biometric(val recoveredArk: ByteArray) : Reauthentication | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
...tity/src/main/kotlin/de/davis/keygo/core/identity/domain/usecase/ChangePasswordUseCase.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package de.davis.keygo.core.identity.domain.usecase | ||
|
|
||
| import de.davis.keygo.core.identity.domain.model.ChangePasswordError | ||
| import de.davis.keygo.core.identity.domain.model.PasswordWrappedArk | ||
| import de.davis.keygo.core.identity.domain.model.Reauthentication | ||
| import de.davis.keygo.core.identity.domain.repository.AccountRepository | ||
| import de.davis.keygo.core.util.Result | ||
| import de.davis.keygo.core.util.resultBinding | ||
| import de.davis.keygo.rust.derive.KeyDeriver | ||
| import de.davis.keygo.rust.derive.deriveRootKekFromPasswordWithResult | ||
| import de.davis.keygo.rust.wrap.KeyWrapper | ||
| import de.davis.keygo.rust.wrap.unwrapAccountRootKeyWithResult | ||
| import de.davis.keygo.rust.wrap.wrapAccountRootKeyWithResult | ||
| import de.davisalessandro.keygo.rust.WrappedKeyBlob | ||
| import org.koin.core.annotation.Single | ||
|
|
||
| @Single | ||
| class ChangePasswordUseCase( | ||
| private val accountRepository: AccountRepository, | ||
| private val keyDeriver: KeyDeriver, | ||
| private val keyWrapper: KeyWrapper, | ||
| ) { | ||
|
|
||
| suspend operator fun invoke( | ||
| reauthentication: Reauthentication, | ||
| newPassword: String, | ||
| ): Result<Unit, ChangePasswordError> = try { | ||
| changePassword(reauthentication, newPassword) | ||
| } finally { | ||
| // We take ownership of the caller-supplied ARK: never leave a copy behind, | ||
| // even when an early validation bails out or re-wrapping fails part-way. | ||
| if (reauthentication is Reauthentication.Biometric) reauthentication.recoveredArk.fill(0) | ||
| } | ||
|
|
||
| private suspend fun changePassword( | ||
| reauthentication: Reauthentication, | ||
| newPassword: String, | ||
| ): Result<Unit, ChangePasswordError> = resultBinding { | ||
| val account = accountRepository.getOrNull() | ||
| ?: return Result.Failure(ChangePasswordError.ActiveAccountNotFound) | ||
|
|
||
| val ark = when (reauthentication) { | ||
| is Reauthentication.Password -> { | ||
| val kek = keyDeriver.deriveRootKekFromPasswordWithResult( | ||
| password = reauthentication.currentPassword, | ||
| salt = account.passwordWrappedArk.salt, | ||
| ).bind { ChangePasswordError.KeyDerivationFailed } | ||
|
|
||
| try { | ||
| keyWrapper.unwrapAccountRootKeyWithResult( | ||
| kek = kek, | ||
| wrapped = WrappedKeyBlob( | ||
| ciphertext = account.passwordWrappedArk.key, | ||
| nonce = account.passwordWrappedArk.keyIV, | ||
| ), | ||
| userId = account.id, | ||
| ).bind { ChangePasswordError.IncorrectPassword } | ||
| } finally { | ||
| kek.fill(0) | ||
| } | ||
| } | ||
|
|
||
| is Reauthentication.Biometric -> { | ||
| account.biometricWrappedArk | ||
| ?: return Result.Failure(ChangePasswordError.BiometricNotEnrolled) | ||
| reauthentication.recoveredArk | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| val newSalt = keyDeriver.generateSalt() | ||
| val newKek = keyDeriver.deriveRootKekFromPasswordWithResult( | ||
| password = newPassword, | ||
| salt = newSalt, | ||
| ).bind { ChangePasswordError.KeyDerivationFailed } | ||
|
|
||
| val rewrapped = try { | ||
| keyWrapper.wrapAccountRootKeyWithResult( | ||
| kek = newKek, | ||
| ark = ark, | ||
| userId = account.id, | ||
| ).bind { ChangePasswordError.WrappingFailed } | ||
| } finally { | ||
| newKek.fill(0) | ||
| } | ||
|
|
||
| accountRepository.set( | ||
| account.copy( | ||
| passwordWrappedArk = PasswordWrappedArk( | ||
| key = rewrapped.ciphertext, | ||
| keyIV = rewrapped.nonce, | ||
| salt = newSalt, | ||
| ), | ||
| ), | ||
| ).bind { ChangePasswordError.PersistenceFailed } | ||
| } finally { | ||
| // Scrub the in-memory ARK on success *and* on every failure path after unwrap. | ||
| ark.fill(0) | ||
| } | ||
| } | ||
| } |
19 changes: 19 additions & 0 deletions
19
...y/src/main/kotlin/de/davis/keygo/core/identity/presentation/BiometricEnrollmentAdapter.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package de.davis.keygo.core.identity.presentation | ||
|
|
||
| import de.davis.keygo.core.identity.domain.model.BiometricEnrollmentError | ||
| import de.davis.keygo.core.security.domain.model.BiometricPolicy | ||
| import de.davis.keygo.core.security.presentation.BiometricCryptoController | ||
| import de.davis.keygo.core.util.Result | ||
|
|
||
| interface BiometricEnrollmentAdapter { | ||
|
|
||
| suspend fun BiometricCryptoController.requestEnableBiometric( | ||
| policy: BiometricPolicy = BiometricPolicy.Default | ||
| ): Result<Unit, BiometricEnrollmentError> | ||
|
|
||
| suspend fun disableBiometric(): Result<Unit, BiometricEnrollmentError> | ||
| } | ||
|
|
||
| inline fun BiometricEnrollmentAdapter.useEnrollmentAdapter( | ||
| block: BiometricEnrollmentAdapter.() -> Result<Unit, BiometricEnrollmentError>, | ||
| ): Result<Unit, BiometricEnrollmentError> = with(this) { block() } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.