Skip to content

Conversation

@WcaleNieWolny
Copy link

@WcaleNieWolny WcaleNieWolny commented Oct 27, 2025

Summary by CodeRabbit

  • New Features

    • Added AuthenticationStrength (NONE/STRONG/WEAK) and requiredStrength option; UI adds STRONG/WEAK verification buttons and a "Use Fallback" checkbox. Availability now reports authentication strength.
  • Bug Fixes / Improvements

    • Better detection and fallback between strong biometrics, weak biometrics, and device credentials; conditional cancel button behavior; web now returns a non-error unavailable result; clearer error mapping.
  • Documentation

    • Docs updated with AuthenticationStrength, BiometricAuthError, and revised availability/options.

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Walkthrough

Replaces biometry-type filtering with a requiredStrength model across platforms, adds STRONG/WEAK/NONE authentication detection and fallback handling, maps native BiometricManager/LocalAuth errors to plugin error codes, and updates examples, web shim, types, and docs to use AuthenticationStrength and BiometricAuthError.

Changes

Cohort / File(s) Summary
Android auth flow
android/src/main/java/ee/forgr/biometric/AuthActivity.java
Replaced allowedBiometryTypes filtering with requiredStrength-driven leftAuthenticators; builder now uses leftAuthenticators; set negative button text only when DEVICE_CREDENTIAL not allowed; removed old helper mapping.
Android availability & error mapping
android/src/main/java/ee/forgr/biometric/NativeBiometric.java
Added AUTH_STRENGTH_* constants; reworked isAvailable to detect STRONG/WEAK/NONE with optional fallback; added convertBiometricManagerErrorToPluginError(int); verifyIdentity and isAvailable now surface authenticationStrength and accept requiredStrength.
iOS availability logic
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift
Switched to authenticationStrength model (STRONG/WEAK/NONE), added fallback checks for device credentials when useFallback is true, and map errors to plugin codes via convertToPluginErrorCode.
Type definitions & public API
src/definitions.ts
Added AuthenticationStrength and BiometricAuthError enums; AvailableResult now returns authenticationStrength and typed errorCode; BiometricOptions replaced allowedBiometryTypes with requiredStrength.
Web shim
src/web.ts
isAvailable() now resolves with { isAvailable: false, authenticationStrength: NONE } instead of throwing.
Docs / README
README.md
Replaced BiometryType usage with AuthenticationStrength; updated API docs to reflect requiredStrength, authenticationStrength, and new BiometricAuthError enum.
Example app — JS wiring & UI
example-app/src/js/biometric-tester.js, example-app/simple-test.html
Extended imports to include AuthenticationStrength; added "Use Fallback" checkbox and STRONG/WEAK verify buttons; pass useFallback and requiredStrength to native API; added getAuthenticationStrengthName helper and updated UI/logs.
Example app — misc
example-app/.gitignore
Added common ignore patterns (.idea, node_modules, .vscode, *.map, .DS_Store, .sourcemaps, dist, .codex).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as Example App / UI
    participant JS as Plugin JS
    participant Native as NativeBiometric (Android/iOS)
    participant BM as BiometricManager / LocalAuth

    App->>JS: isAvailable({ useFallback })
    JS->>Native: isAvailable(useFallback)
    activate Native
    Native->>BM: canAuthenticate(BIOMETRIC_STRONG)
    alt Strong available
        BM-->>Native: SUCCESS
        Native-->>JS: { isAvailable: true, authenticationStrength: STRONG }
    else Strong unavailable
        BM-->>Native: FAIL/ERROR
        alt useFallback == true
            Native->>BM: canAuthenticate(BIOMETRIC_WEAK)
            alt Weak available
                BM-->>Native: SUCCESS
                Native-->>JS: { isAvailable: true, authenticationStrength: WEAK }
            else Weak unavailable
                Native->>BM: canAuthenticate(DEVICE_CREDENTIAL)
                BM-->>Native: SUCCESS/FAIL/ERROR
                Native-->>JS: { isAvailable: maybe, authenticationStrength: NONE/WEAK, errorCode }
            end
        else
            Native-->>JS: { isAvailable: false, authenticationStrength: NONE, errorCode }
        end
    end
    deactivate Native
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • android/src/main/java/ee/forgr/biometric/NativeBiometric.java — correctness of strong vs weak checks, fallback logic, and BiometricManager → BiometricAuthError mapping.
    • android/src/main/java/ee/forgr/biometric/AuthActivity.javaleftAuthenticators selection and conditional negative button handling.
    • src/definitions.ts and README.md — ensure enum values/names align with native code and docs.
    • Example app files — verify useFallback and requiredStrength are passed and UI shows strength names correctly.

Possibly related PRs

  • feat: example app #60 — overlaps changes to the example app UI/JS and import signatures; likely to conflict with example-app modifications.

Poem

🐇
I hop through code with careful pace,
Strong and weak find their proper place.
Fingers, faces, PINs align—
Left authenticators choose the sign.
A tiny hop — authentication, fine!

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: face unlock on devices that do not consider face to be level 3" refers to a genuine aspect of the changes in the PR. The changeset includes extensive refactoring to support authentication strength levels (STRONG, WEAK, NONE) throughout the codebase, which directly addresses the scenario where devices classify face biometrics as weak rather than strong. However, the title is somewhat narrow and specific to this particular use case. The main architectural change—replacing biometry-type-based filtering with strength-based filtering across Android, iOS, and web implementations—is not captured by the title. A developer reviewing the commit history might expect changes limited to face recognition handling rather than the comprehensive system-wide refactoring that is actually present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_face_unlock_on_weak_devices

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8b281a and a87f4d7.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java
🧬 Code graph analysis (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)
src/web.ts (1)
  • isAvailable (19-24)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)
  • isAvailable (38-83)
🪛 GitHub Actions: Build source code and test it
android/src/main/java/ee/forgr/biometric/NativeBiometric.java

[error] 467-467: cannot find symbol: BiometricManager.BIOMETRIC_ERROR_LOCKOUT_PERMANENT


[error] 473-473: cannot find symbol: BiometricManager.BIOMETRIC_ERROR_LOCKOUT

🔇 Additional comments (3)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (3)

64-67: LGTM! Authentication strength constants align with cross-platform model.

The constants are well-defined and consistent with the iOS and web implementations shown in the relevant code snippets.


87-118: Excellent refactor - strength-based detection resolves previous face misclassification.

The new logic properly differentiates between strong and weak biometrics without conflating capability with modality. This addresses the critical issues from previous reviews where face authentication was incorrectly inferred from generic weak biometric availability.


174-180: LGTM! Properly propagates requiredStrength to authentication flow.

The migration from allowedBiometryTypes to requiredStrength is consistent with the cross-platform authentication strength model.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@WcaleNieWolny WcaleNieWolny marked this pull request as ready for review October 27, 2025 14:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)

203-211: Unsafe cast from JSArray to int can crash.

allowedBiometryTypes items from JS arrive as Number (Double) or String. (int) allowedTypes.toList().get(i) can throw ClassCastException.

Apply:

-            for (int i = 0; i < allowedTypes.length(); i++) {
-                types[i] = (int) allowedTypes.toList().get(i);
-            }
+            for (int i = 0; i < allowedTypes.length(); i++) {
+                Object v = allowedTypes.toList().get(i);
+                if (v instanceof Number) {
+                    types[i] = ((Number) v).intValue();
+                } else if (v instanceof String) {
+                    try {
+                        types[i] = Integer.parseInt((String) v);
+                    } catch (NumberFormatException ignore) {
+                        // skip invalid entry
+                    }
+                }
+            }
🧹 Nitpick comments (5)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (3)

136-153: Availability flow is reasonable; consider not overriding BiometricManager’s error.

When useFallback is true but the device has no credentials, you force canAuthenticateResult = BIOMETRIC_ERROR_HW_UNAVAILABLE. That can mask more accurate statuses (e.g., NONE_ENROLLED).

Suggestion: keep canAuthenticateResult as returned by BiometricManager and use the separate fallbackAvailable flag to compute isAvailable. This preserves error fidelity.

If you prefer a distinct “no device credential” signal, introduce a plugin‑side code (e.g., 14) for this path to mirror BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL semantics.


169-171: Error-code mapping: verify SECURITY_UPDATE_REQUIRED and add UNKNOWN mapping.

  • SECURITY_UPDATE_REQUIRED mapped to USER_LOCKOUT (2) seems semantically wrong; consider BIOMETRICS_UNAVAILABLE or a dedicated “security update required” plugin code.
  • Add a case for BIOMETRIC_STATUS_UNKNOWN to return UNKNOWN_ERROR (0) explicitly.

Would you like me to align these mappings with the README error table and iOS parity?

Also applies to: 471-489


173-175: Type reporting may say “Biometrics Available (None)” in fallback-only cases.

When only device credentials are available, isAvailable is true but biometryType is NONE. Consider exposing a second flag like hasBiometrics to avoid confusing UIs.

example-app/src/js/biometric-tester.js (1)

244-261: Clarify UI when only device credentials are available.

Avoid “Biometrics Available (None)” wording in fallback‑only cases.

Apply:

-      if (result.isAvailable) {
-        elements.biometricStatus.textContent = `✅ Biometrics Available (${getBiometryTypeName(result.biometryType)})`;
+      if (result.isAvailable) {
+        const onlyDc = result.biometryType === 0 && useFallback;
+        const label = onlyDc ? 'Device Credential Only' : getBiometryTypeName(result.biometryType);
+        elements.biometricStatus.textContent = `✅ ${onlyDc ? 'Authentication Available' : 'Biometrics Available'} (${label})`;
         elements.biometricStatus.className = 'status available';
         elements.biometricInfo.style.display = 'block';
         elements.biometricInfo.innerHTML = `<strong>Biometry Type:</strong> ${getBiometryTypeName(result.biometryType)} (${result.biometryType})`;
       } else {
android/src/main/java/ee/forgr/biometric/AuthActivity.java (1)

159-179: FACE → STRONG|WEAK approach is verified; consider optional DEVICE_CREDENTIAL fallback logic.

AndroidX Biometric 1.1.0 supports BiometricManager.Authenticators.BIOMETRIC_WEAK in setAllowedAuthenticators() and canAuthenticate(int). The OR'ing of BIOMETRIC_STRONG with BIOMETRIC_WEAK for face authentication is a valid approach for Samsung weak-face device compatibility.

Optional: If you want face-only authentication with device credential fallback when useFallback is true, consider adding logic to OR DEVICE_CREDENTIAL when allowedTypes is provided, rather than always relying on the fallback parameter alone.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 714ed4b and a2598ef.

📒 Files selected for processing (3)
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java (2 hunks)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (4 hunks)
  • example-app/src/js/biometric-tester.js (8 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: below43
PR: Cap-go/capacitor-native-biometric#41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
PR: Cap-go/capacitor-native-biometric#41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/AuthActivity.java
🧬 Code graph analysis (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)
src/web.ts (1)
  • isAvailable (17-19)
🔇 Additional comments (5)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)

73-79: Signature change looks good.

Passing useFallback into getAvailableFeature keeps availability/type detection consistent with the new flow.

android/src/main/java/ee/forgr/biometric/AuthActivity.java (2)

48-57: Good separation of authenticators.

leftAuthenticators correctly folds in DEVICE_CREDENTIAL only when requested and applies allowed types filtering before building the prompt.


58-65: Negative button handling is correct.

Setting negative text only when DEVICE_CREDENTIAL is not allowed avoids builder exceptions and ensures a non‑null label. This also aligns with our prior learning about null negativeButtonText causing Android errors. Based on learnings.

Please confirm no paths build PromptInfo without a negative button when DEVICE_CREDENTIAL is absent.

example-app/src/js/biometric-tester.js (2)

117-123: Nice UX addition for fallback toggle.

The checkbox wiring cleanly exercises the new isAvailable({ useFallback }) path.


235-237: Face-only path is correctly limited.

Passing [BiometryType.FACE_AUTHENTICATION] ensures Android uses face (weak or strong) where available.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)

97-114: Face detection remains incorrectly classified (critical issue from previous review).

This is the same issue flagged in the previous review. Checking canAuthenticate(BIOMETRIC_WEAK) will return BIOMETRIC_SUCCESS on any device with strong fingerprint support, because strong biometrics satisfy weak requirements. This causes fingerprint-only devices to incorrectly report face authentication support.

Face presence must be determined exclusively by PackageManager.FEATURE_FACE. Remove lines 104-114.

Apply this diff:

-            // If fallback is enabled, also check for weak face authentication (like Samsung)
-            if (useFallback && !hasFace) {
-                try {
-                    int faceWeakTest = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
-                    if (faceWeakTest == BiometricManager.BIOMETRIC_SUCCESS) {
-                        hasFace = true;
-                    }
-                } catch (Exception e) {
-                    System.out.println("Error checking face authentication: " + e.getMessage());
-                }
-            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2598ef and 4921cf3.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)
src/web.ts (1)
  • isAvailable (17-19)
🔇 Additional comments (2)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)

146-154: Weak biometric fallback logic looks sound for capability detection.

The logic here correctly checks whether the device can authenticate using weak biometrics and device credentials. This is appropriate for determining authentication capability (as opposed to determining which specific biometric types are available).


491-509: Verify the SECURITY_UPDATE_REQUIRED error mapping.

The mapping of BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED to error code 2 (USER_LOCKOUT) seems semantically incorrect. A security update requirement is a different condition than a user lockout.

Consider mapping this to BIOMETRICS_UNAVAILABLE (1) instead, or introducing a distinct error code if the plugin supports it.

Can you verify the intended error mapping for security update requirements? You may want to check the plugin's error code definitions and client expectations.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (3)

75-86: Fallback logic still conflates capability with modality detection.

Using canAuthenticate(BIOMETRIC_WEAK) to check availability is reasonable, but this result then flows into type detection logic (lines 88-126) where it influences hasFace, hasFinger, etc. The previous review correctly identified that capability checks (can authenticate?) should be separated from modality detection (which biometric types are present?).

Modality should rely exclusively on PackageManager.hasSystemFeature flags, not on canAuthenticate results.


97-114: Face detection is still misclassified when any weak biometric is available.

Lines 104-114 set hasFace = true when canAuthenticate(BIOMETRIC_WEAK) returns SUCCESS. However, BIOMETRIC_WEAK succeeds on devices with strong fingerprint (since strong biometrics satisfy weak checks). This causes fingerprint-only devices to be incorrectly reported as having face authentication.

The previous review identified this exact issue. Biometric type must be determined exclusively by system feature flags (PackageManager.FEATURE_FACE), not by capability checks.

Remove the fallback block that infers face from BIOMETRIC_WEAK:

-            // If fallback is enabled, also check for weak face authentication (like Samsung)
-            if (useFallback && !hasFace) {
-                try {
-                    int faceWeakTest = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
-                    if (faceWeakTest == BiometricManager.BIOMETRIC_SUCCESS) {
-                        hasFace = true;
-                    }
-                } catch (Exception e) {
-                    System.out.println("Error checking face authentication: " + e.getMessage());
-                }
-            }

175-175: Biometry type will be incorrect due to flawed face detection.

This line returns the result from getAvailableFeature(useFallback), which contains flawed face-detection logic (lines 104-114) that misclassifies fingerprint-only devices as having face authentication.

Once lines 104-114 are corrected to detect face exclusively via FEATURE_FACE, this line will return accurate biometry types.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4921cf3 and 9e1243b.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
PR: Cap-go/capacitor-native-biometric#41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java
🧬 Code graph analysis (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)
  • isAvailable (38-72)
🔇 Additional comments (3)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (3)

146-154: Availability check is reasonable but type detection remains flawed.

Using BIOMETRIC_WEAK to determine availability (can any biometric authenticate?) is appropriate. However, at line 175, biometryType is computed using getAvailableFeature(useFallback), which contains flawed face-detection logic (lines 104-114) that will misreport types.

Once the face detection logic is corrected to rely solely on FEATURE_FACE, this availability check will work correctly.


161-164: LGTM: Error handling for unavailable fallback.

The logic correctly marks authentication as unavailable when fallback is requested but not available and no biometrics are present.


170-172: Good refactor: Centralized error mapping.

Extracting error code conversion into a dedicated helper method improves maintainability.

@WcaleNieWolny
Copy link
Author

@coderabbitai - can you check the comments you posted and see if they still apply after the latest commit?

@coderabbitai
Copy link

coderabbitai bot commented Nov 2, 2025

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments made by coderabbitai on PR #62
gh pr view 62 --json reviews,comments --jq '
  (.reviews[] | select(.author.login == "coderabbitai") | {
    state: .state,
    body: .body,
    submittedAt: .submittedAt
  }),
  (.comments[] | select(.author.login == "coderabbitai") | {
    body: .body,
    createdAt: .createdAt
  })
'

echo "---SEPARATOR---"

# Fetch review comments (inline comments) made by coderabbitai
gh api "/repos/{owner}/{repo}/pulls/62/comments" --jq '.[] | select(.user.login == "coderabbitai") | {
  path: .path,
  line: .line,
  body: .body,
  created_at: .created_at,
  position: .position,
  original_position: .original_position,
  diff_hunk: .diff_hunk
}'

Length of output: 15


🏁 Script executed:

#!/bin/bash
# Check all activity on PR #62 to understand if any reviews were done
gh pr view 62 --json reviews,comments,timelineItems --jq '{
  total_reviews: (.reviews | length),
  total_comments: (.comments | length),
  reviews: [.reviews[] | {author: .author.login, state: .state, submittedAt: .submittedAt}],
  comments: [.comments[] | {author: .author.login, createdAt: .createdAt}]
}'

echo "---SEPARATOR---"

# Check for any inline review comments
gh api "/repos/{owner}/{repo}/pulls/62/comments" --jq '[.[] | {author: .user.login, path: .path, created_at: .created_at}] | length'

Length of output: 692


🏁 Script executed:

#!/bin/bash
# Check reviews and comments on PR #62 with valid fields
gh pr view 62 --json reviews,comments,latestReviews --jq '{
  total_reviews: (.reviews | length),
  total_comments: (.comments | length),
  reviews: [.reviews[] | {author: .author.login, state: .state, submittedAt: .submittedAt}],
  comments: [.comments[] | {author: .author.login, body: .body[0:100], createdAt: .createdAt}],
  latestReviews: [.latestReviews[] | {author: .author.login, state: .state}]
}'

Length of output: 84


@WcaleNieWolny, I checked the PR and found that I haven't posted any comments or reviews on PR #62 yet. There are no previous comments from me to verify against the latest commit.

Would you like me to conduct a fresh review of the changes in this PR? If so, just let me know and I'll review the code changes, or you can use the `` tag to trigger an automatic review.

((\
(-.-)
o_(")(") curious

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@WcaleNieWolny
Copy link
Author

@coderabbitai - fine, rereview this PR :)

@coderabbitai
Copy link

coderabbitai bot commented Nov 2, 2025

@WcaleNieWolny, I'll conduct a complete review of this PR for you! Let me go through all the changes related to the face unlock fix on devices that don't consider face authentication as level 3 biometric. 🔍

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)

55-80: Fallback error is dropped; wrong errorCode returned.

If useFallback is true and the passcode/PIN check fails (e.g., LAError.passcodeNotSet), deviceError captures that failure but we continue to surface the earlier biometric error. The promise resolves with errorCode describing biometrics instead of the missing credentials, breaking the new BiometricAuthError.PASSCODE_NOT_SET contract.

Please carry the fallback error forward before the final guard.

-        if useFallback {
-            let devicePolicy = LAPolicy.deviceOwnerAuthentication
-            var deviceError: NSError?
-            hasDeviceCredentials = context.canEvaluatePolicy(devicePolicy, error: &deviceError)
-        }
+        if useFallback {
+            let devicePolicy = LAPolicy.deviceOwnerAuthentication
+            var deviceError: NSError?
+            hasDeviceCredentials = context.canEvaluatePolicy(devicePolicy, error: &deviceError)
+            if !hasDeviceCredentials, let deviceError {
+                error = deviceError
+            }
+        }
README.md (1)

22-46: Update sample to use authenticationStrength.

The top-level example still imports BiometryType and reads result.biometryType, which no longer exists after this PR. Anyone copying the snippet will hit a TS error at build time.

Please switch the import/usage to AuthenticationStrength and result.authenticationStrength (with the new helper) to keep the README accurate.

♻️ Duplicate comments (2)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)

109-118: Stop inferring face from generic WEAK success.

This reintroduces the misclassification we discussed earlier: BIOMETRIC_STRONG sensors also satisfy BIOMETRIC_WEAK, so fingerprint‑only devices will now be reported as having face auth once useFallback is true.

Please drop the weak-canAuthenticate fallback when determining hasFace; rely solely on FEATURE_FACE.

-        if (useFallback && !hasFace) {
-            try {
-                int faceWeakTest = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
-                if (faceWeakTest == BiometricManager.BIOMETRIC_SUCCESS) {
-                    hasFace = true;
-                }
-            } catch (Exception e) {
-                System.out.println("Error checking face authentication: " + e.getMessage());
-            }
-        }
+        // Keep face detection bound to the platform feature flag only.

523-525: Map SECURITY_UPDATE_REQUIRED to biometrics unavailable.

BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED means the sensor is disabled until a security patch lands; it is not a user lockout. Returning 2 misleads consumers about why auth failed.

Switch this case to return 1 (BIOMETRICS_UNAVAILABLE).

-            case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
-                return 2; // USER_LOCKOUT
+            case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+                return 1; // BIOMETRICS_UNAVAILABLE
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1243b and 6d0d280.

📒 Files selected for processing (9)
  • README.md (3 hunks)
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java (1 hunks)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (6 hunks)
  • example-app/.gitignore (1 hunks)
  • example-app/simple-test.html (3 hunks)
  • example-app/src/js/biometric-tester.js (9 hunks)
  • ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (2 hunks)
  • src/definitions.ts (4 hunks)
  • src/web.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • example-app/.gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java
🧬 Code graph analysis (2)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)
  • isAvailable (38-83)
src/web.ts (1)
  • isAvailable (19-24)
example-app/src/js/biometric-tester.js (2)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)
  • verifyIdentity (85-125)
src/web.ts (1)
  • verifyIdentity (26-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_android
🔇 Additional comments (1)
example-app/src/js/biometric-tester.js (1)

271-287: Strength-aware verification flow looks correct.
Conditionally adding requiredStrength only when the user picks a strength keeps the default verification behavior unchanged while letting us exercise the new weak/strong paths in the tester. Nice balance between backwards compatibility and new coverage.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (1)

109-119: Face detection is still misclassified when any weak biometric is available.

This is the same critical issue flagged in previous reviews. Setting hasFace = true because canAuthenticate(BIOMETRIC_WEAK) == SUCCESS will incorrectly report face on fingerprint-only devices, since strong fingerprint also satisfies the weak check.

Per previous review guidance, rely exclusively on FEATURE_FACE for face detection:

-            // If fallback is enabled, also check for weak face authentication (like Samsung)
-            if (useFallback && !hasFace) {
-                try {
-                    int faceWeakTest = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
-                    if (faceWeakTest == BiometricManager.BIOMETRIC_SUCCESS) {
-                        hasFace = true;
-                    }
-                } catch (Exception e) {
-                    System.out.println("Error checking face authentication: " + e.getMessage());
-                }
-            }
+            // Do not infer face presence from BIOMETRIC_WEAK; fingerprint (STRONG) also satisfies WEAK.
+            // Only trust the system feature flag FEATURE_FACE for face availability.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0d280 and 83ae346.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java
🧬 Code graph analysis (1)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)
src/web.ts (1)
  • isAvailable (19-24)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)
  • isAvailable (38-83)
🪛 GitHub Actions: Build source code and test it
android/src/main/java/ee/forgr/biometric/NativeBiometric.java

[error] 523-523: cannot find symbol: BiometricManager.BIOMETRIC_ERROR_LOCKOUT_PERMANENT


[error] 529-529: cannot find symbol: BiometricManager.BIOMETRIC_ERROR_LOCKOUT

🔇 Additional comments (4)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (4)

64-67: LGTM! Authentication strength constants align with cross-platform API.

The constants are clear and consistent with the iOS implementation.


144-174: Well-designed separation of strong/weak biometric detection.

The logic correctly checks strong and weak biometrics independently, then determines authentication strength based on availability. The distinction between biometric-backed (STRONG/WEAK) and credential-based (WEAK) authentication is clear.


230-236: LGTM! Required strength parameter properly wired.

The parameter is correctly passed to AuthActivity with appropriate null handling.


176-196: Error handling logic is sound.

The error prioritization (strong → weak → default) is correct. However, ensure the convertBiometricManagerErrorToPluginError method is fixed first to properly map error codes.

@coderabbitai
Copy link

coderabbitai bot commented Nov 2, 2025

@WcaleNieWolny, I'll conduct a complete review of all the changes in this PR! 🔍

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (1)

53-81: Propagate fallback errors instead of always reporting biometric failures

When useFallback is true and the device lacks both enrolled biometrics and a passcode/PIN, context.canEvaluatePolicy for biometrics fails first (setting error to something like LAError.biometryNotAvailable). We then check device credentials, but the local deviceError goes out of scope and we ignore it. The final branch therefore always reports the biometric error code, masking situations such as LAError.passcodeNotSet, so callers never learn why fallback failed. Please keep the fallback error around and prefer it when present.

-        if useFallback {
+        var fallbackError: NSError?
+        if useFallback {
             let devicePolicy = LAPolicy.deviceOwnerAuthentication
-            var deviceError: NSError?
-            hasDeviceCredentials = context.canEvaluatePolicy(devicePolicy, error: &deviceError)
+            hasDeviceCredentials = context.canEvaluatePolicy(devicePolicy, error: &fallbackError)
         }
 …
-            guard let authError = error else {
+            let finalError = fallbackError ?? error
+            guard let authError = finalError else {
                 obj["errorCode"] = 0
                 call.resolve(obj)
                 return
             }
-            var pluginErrorCode = convertToPluginErrorCode(authError.code)
-            obj["errorCode"] = pluginErrorCode
+            let pluginErrorCode = convertToPluginErrorCode(authError.code)
+            obj["errorCode"] = pluginErrorCode
♻️ Duplicate comments (2)
android/src/main/java/ee/forgr/biometric/NativeBiometric.java (2)

109-119: Face detection is still incorrectly inferred from weak biometric availability.

This issue was previously flagged but the problematic code remains. Setting hasFace = true when canAuthenticate(BIOMETRIC_WEAK) succeeds will misclassify fingerprint-only devices as having face authentication, since strong fingerprint also satisfies the weak biometric requirement.

Remove the fallback block that infers face presence from BIOMETRIC_WEAK. Face availability should be determined exclusively by PackageManager.FEATURE_FACE:

-            // If fallback is enabled, also check for weak face authentication (like Samsung)
-            if (useFallback && !hasFace) {
-                try {
-                    int faceWeakTest = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
-                    if (faceWeakTest == BiometricManager.BIOMETRIC_SUCCESS) {
-                        hasFace = true;
-                    }
-                } catch (Exception e) {
-                    System.out.println("Error checking face authentication: " + e.getMessage());
-                }
-            }

514-536: Compilation failure: BiometricManager lockout constants don't exist.

The pipeline confirms that BIOMETRIC_ERROR_LOCKOUT and BIOMETRIC_ERROR_LOCKOUT_PERMANENT are BiometricPrompt constants, not BiometricManager constants. BiometricManager.canAuthenticate() never returns these lockout codes—they are only reported during authentication via BiometricPrompt callbacks in AuthActivity.

Additionally, mapping SECURITY_UPDATE_REQUIRED to USER_LOCKOUT is semantically incorrect. This error indicates the biometric sensor is blocked due to a security vulnerability, not user lockout.

Apply this fix:

     private int convertBiometricManagerErrorToPluginError(int errorCode) {
         switch (errorCode) {
             case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
             case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
                 return 1; // BIOMETRICS_UNAVAILABLE
-            case BiometricManager.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
-                return 2; // USER_LOCKOUT (permanent lockout)
             case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
-                return 2; // USER_LOCKOUT
+                return 1; // BIOMETRICS_UNAVAILABLE
             case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
                 return 3; // BIOMETRICS_NOT_ENROLLED
-            case BiometricManager.BIOMETRIC_ERROR_LOCKOUT:
-                return 4; // USER_TEMPORARY_LOCKOUT (temporary lockout, 30 seconds)
             case BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED:
                 return 1; // BIOMETRICS_UNAVAILABLE
             default:
                 return 0; // UNKNOWN_ERROR
         }
     }

Note: Lockout states must be handled in AuthActivity's BiometricPrompt.AuthenticationCallback, where these error codes are actually produced.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 378a246 and 83ae346.

📒 Files selected for processing (9)
  • README.md (3 hunks)
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java (1 hunks)
  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java (6 hunks)
  • example-app/.gitignore (1 hunks)
  • example-app/simple-test.html (3 hunks)
  • example-app/src/js/biometric-tester.js (9 hunks)
  • ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift (2 hunks)
  • src/definitions.ts (4 hunks)
  • src/web.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.
📚 Learning: 2025-02-13T20:05:24.035Z
Learnt from: below43
Repo: Cap-go/capacitor-native-biometric PR: 41
File: android/src/main/java/ee/forgr/biometric/AuthActivity.java:75-82
Timestamp: 2025-02-13T20:05:24.035Z
Learning: In android/src/main/java/ee/forgr/biometric/AuthActivity.java, the negativeButtonText must be set regardless of the useFallback value to prevent Android errors when negativeButtonText is null.

Applied to files:

  • android/src/main/java/ee/forgr/biometric/NativeBiometric.java
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java
🪛 GitHub Actions: Build source code and test it
android/src/main/java/ee/forgr/biometric/NativeBiometric.java

[error] 523-523: Compilation error: cannot find symbol BiometricManager.BIOMETRIC_ERROR_LOCKOUT_PERMANENT


[error] 529-529: Compilation error: cannot find symbol BiometricManager.BIOMETRIC_ERROR_LOCKOUT


[warning] 1-1: Gradle build produced warnings about obsolete Java 8 source/target compatibility; consider configuring Java toolchain or upgrading source/target compatibility.

android/src/main/java/ee/forgr/biometric/AuthActivity.java

[warning] 1-1: Gradle build produced warnings about obsolete Java 8 source/target compatibility; consider configuring Java toolchain or upgrading source/target compatibility.

🔇 Additional comments (8)
example-app/.gitignore (1)

1-8: Clean and comprehensive ignore patterns.

The .gitignore file covers all standard categories for a Node.js/web project: IDE/editor settings, dependencies, build output, system files, and source maps. The patterns are appropriate and consistent with best practices.

src/definitions.ts (4)

18-33: LGTM! Well-documented authentication strength enum.

The AuthenticationStrength enum is clearly defined with comprehensive documentation explaining the distinction between STRONG, WEAK, and NONE levels. The clarification that PIN/pattern/password is always WEAK (never STRONG) is particularly helpful for API consumers.


47-65: LGTM! Clear interface changes align with strength-based model.

The AvailableResult interface properly replaces the previous biometry-type approach with the new authenticationStrength field and uses the BiometricAuthError enum for better error handling.


89-95: LGTM! Clear documentation for the new strength-based option.

The replacement of allowedBiometryTypes with requiredStrength is well-documented and aligns with the PR's objective to support devices that classify face authentication differently.


127-192: LGTM! Comprehensive error code documentation.

The BiometricAuthError enum provides detailed documentation for each error code, including platform availability. This will significantly improve the developer experience when handling authentication errors.

android/src/main/java/ee/forgr/biometric/NativeBiometric.java (3)

64-67: LGTM! Constants align with TypeScript enum.

The authentication strength constants are correctly defined and match the AuthenticationStrength enum values from definitions.ts.


137-202: LGTM! Well-structured strength-based availability check.

The refactored isAvailable method properly:

  • Separates strong and weak biometric checks
  • Correctly determines authentication strength (STRONG for strong biometrics, WEAK for weak biometrics or PIN/pattern/password)
  • Handles error codes appropriately when authentication is unavailable
  • Prioritizes strong biometric errors over weak biometric errors in error reporting

This is a solid implementation of the strength-based authentication model.


230-236: LGTM! Proper replacement of biometry type filtering with strength-based filtering.

The code correctly passes requiredStrength to AuthActivity and handles the nullable integer appropriately. This aligns with the PR's objective to support strength-based authentication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants