fix(biometric) Incorrect error reported on Android with no device passcode set and fallback allowed (#2648)

* Fix incorrect error being reported in certain cases with fallback allowed

* Add change file

* Rework original solution due to overlooking authenticators logic on API >= `Build.VERSION_CODES.R`

* fix(biometric): don't cast the Android error message to String

* fix(biometric): report a missing device credential before showing the Android prompt

Replaces the override of ERROR_NO_BIOMETRICS in the prompt callback, which keyed on
the caller's flag rather than the device state and missed the no-hardware errors.

* fix(biometric): load the missing device credential message from a string resource

* chore(biometric): drop stray semicolon

* docs(biometric): mention the passcodeNotSet error code rename in the change file

* test(e2e): cover the biometric passcodeNotSet rejection on Android

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Patrick F.andLucas Nogueira authored and GitHub committed 2026-10-11 21:56:38 -03:00
1 parent 2ebe54abce
commit dff0dd4b23
6 files changed
+102 -34

No files matched your search

@@ -0,0 +1,52 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import path from 'node:path'
import { spawnSync } from 'node:child_process'
import { expect } from '@wdio/globals'
import { tauri, describePlugin, platform } from '../helpers/index.js'
// A successful authentication needs a finger or a face, which the driver cannot
// provide, so only the paths that reject without showing the prompt are covered.
/**
* Whether the Android device has no lock screen credential (the default on a
* fresh emulator). `locksettings verify` succeeds without a credential only then.
*/
function androidHasNoLockCredential(): boolean {
const sdk = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT
const adb = sdk ? path.join(sdk, 'platform-tools', 'adb') : 'adb'
const device = process.env.E2E_ANDROID_DEVICE
? ['-s', process.env.E2E_ANDROID_DEVICE]
: []
const result = spawnSync(
adb,
[...device, 'shell', 'locksettings', 'verify'],
{ encoding: 'utf8', timeout: 20_000 }
)
return result.status === 0 && /verified successfully/.test(result.stdout)
}
describePlugin('biometric', { mobileOnly: true }, () => {
// Android only, where the lock screen state can be read over adb.
const itNoLockCredential =
platform === 'android' && androidHasNoLockCredential() ? it : it.skip
itNoLockCredential(
'rejects with passcodeNotSet when the fallback is allowed and no credential is set',
async () => {
const code = await tauri(async (api) => {
try {
await api.biometric.authenticate('e2e', {
allowDeviceCredential: true
})
return 'resolved'
} catch (error) {
return (error as { code?: string }).code ?? null
}
})
expect(code).toBe('passcodeNotSet')
}
)
})