diff --git a/README.md b/README.md
index 49a97dcf..f9f6aa44 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ It uses the same internal APIs as `adb backup` which is deprecated and thus need
* `android.permission.ACCESS_MEDIA_LOCATION` to backup original media files e.g. without stripped EXIF metadata.
* `android.permission.FOREGROUND_SERVICE` to do periodic storage backups without interruption.
* `android.permission.MANAGE_DOCUMENTS` to retrieve the available storage roots (optional) for better UX.
+* `android.permission.USE_BIOMETRIC` to authenticate saving a new recovery code
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault.
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c8a8da5e..d3fa7189 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -47,6 +47,9 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
+
+
+
= 30 && keyguardManager.isDeviceSecure) {
+ // if we have a lock-screen secret, we can ask for it before storing the code
+ storeNewCodeAfterAuth(input)
+ } else {
+ // user doesn't seem to care about security, store key without auth
+ viewModel.storeNewCode(input)
+ }
} else {
viewModel.verifyExistingCode(input)
}
}
+ @RequiresApi(30)
+ private fun storeNewCodeAfterAuth(input: List) {
+ val biometricPrompt = BiometricPrompt.Builder(context)
+ .setConfirmationRequired(true)
+ .setTitle(getString(R.string.recovery_code_auth_title))
+ .setDescription(getString(R.string.recovery_code_auth_description))
+ // BIOMETRIC_STRONG could be made optional in the future, setting guarded by credentials
+ .setAllowedAuthenticators(DEVICE_CREDENTIAL or BIOMETRIC_STRONG)
+ .build()
+ val callback = object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
+ viewModel.storeNewCode(input)
+ }
+ }
+ biometricPrompt.authenticate(CancellationSignal(), getMainExecutor(context), callback)
+ }
+
private fun allFilledOut(input: List): Boolean {
for (i in input.indices) {
if (input[i].isNotEmpty()) continue
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7682402e..046ee7ac 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -94,6 +94,8 @@
Wait one second…
Generating a new code will make your existing backups inaccessible. We\'ll try to delete them if possible.\n\nAre you sure you want to do this?
New recovery code has been created successfully
+ Confirm it\'s really you
+ This ensures that nobody else can get your data when finding your phone unlocked.
Backup notification