snapshot of https://github.com/tauri-apps/plugins-workspace.git (v2, 2026-09-23; blobs > 131072B omitted)
Some checks failed
integration tests / run-integration-tests (windows-latest) (push) Has been cancelled
Test Rust / test (push) Has been cancelled
test plugins e2e (mobile) / ios (push) Has been cancelled
test plugins e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test plugins e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
integration tests / run-integration-tests (macos-latest) (push) Has been cancelled
version or publish / version-or-publish (push) Failing after 0s
integration tests / run-integration-tests (ubuntu-22.04) (push) Failing after 0s
Lint JavaScript / eslint (push) Failing after 0s
Sync / sync-to-mirrors (push) Failing after 0s
test plugins e2e (mobile) / android (push) Failing after 0s
test plugins e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
Test Rust / changes (push) Failing after 0s
Lint Rust / changes (push) Failing after 0s
Lint Rust / clippy (push) Has been skipped
Audit Rust / audit-rust (push) Failing after 1s
Audit JavaScript / audit-js (push) Failing after 1s
Some checks failed
integration tests / run-integration-tests (windows-latest) (push) Has been cancelled
Test Rust / test (push) Has been cancelled
test plugins e2e (mobile) / ios (push) Has been cancelled
test plugins e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test plugins e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
integration tests / run-integration-tests (macos-latest) (push) Has been cancelled
version or publish / version-or-publish (push) Failing after 0s
integration tests / run-integration-tests (ubuntu-22.04) (push) Failing after 0s
Lint JavaScript / eslint (push) Failing after 0s
Sync / sync-to-mirrors (push) Failing after 0s
test plugins e2e (mobile) / android (push) Failing after 0s
test plugins e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
Test Rust / changes (push) Failing after 0s
Lint Rust / changes (push) Failing after 0s
Lint Rust / clippy (push) Has been skipped
Audit Rust / audit-rust (push) Failing after 1s
Audit JavaScript / audit-js (push) Failing after 1s
This commit is contained in:
153
plugins/biometric/ios/Sources/BiometricPlugin.swift
Normal file
153
plugins/biometric/ios/Sources/BiometricPlugin.swift
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import LocalAuthentication
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
class BiometricStatus {
|
||||
let available: Bool
|
||||
let biometryType: LABiometryType
|
||||
let errorReason: String?
|
||||
let errorCode: String?
|
||||
|
||||
init(available: Bool, biometryType: LABiometryType, errorReason: String?, errorCode: String?) {
|
||||
self.available = available
|
||||
self.biometryType = biometryType
|
||||
self.errorReason = errorReason
|
||||
self.errorCode = errorCode
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthOptions: Decodable {
|
||||
let reason: String
|
||||
var allowDeviceCredential: Bool?
|
||||
var fallbackTitle: String?
|
||||
var cancelTitle: String?
|
||||
}
|
||||
|
||||
class BiometricPlugin: Plugin {
|
||||
let authenticationErrorCodeMap: [Int: String] = [
|
||||
0: "",
|
||||
LAError.appCancel.rawValue: "appCancel",
|
||||
LAError.authenticationFailed.rawValue: "authenticationFailed",
|
||||
LAError.invalidContext.rawValue: "invalidContext",
|
||||
LAError.notInteractive.rawValue: "notInteractive",
|
||||
LAError.passcodeNotSet.rawValue: "passcodeNotSet",
|
||||
LAError.systemCancel.rawValue: "systemCancel",
|
||||
LAError.userCancel.rawValue: "userCancel",
|
||||
LAError.userFallback.rawValue: "userFallback",
|
||||
LAError.biometryLockout.rawValue: "biometryLockout",
|
||||
LAError.biometryNotAvailable.rawValue: "biometryNotAvailable",
|
||||
LAError.biometryNotEnrolled.rawValue: "biometryNotEnrolled",
|
||||
]
|
||||
|
||||
var status: BiometricStatus!
|
||||
|
||||
public override func load(webview: WKWebView) {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
var available = context.canEvaluatePolicy(
|
||||
.deviceOwnerAuthenticationWithBiometrics, error: &error)
|
||||
var reason: String? = nil
|
||||
var errorCode: String? = nil
|
||||
|
||||
if available && context.biometryType == .faceID {
|
||||
let entry = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String
|
||||
|
||||
if entry == nil || entry?.count == 0 {
|
||||
available = false
|
||||
reason = "NSFaceIDUsageDescription is not in the app Info.plist"
|
||||
errorCode = authenticationErrorCodeMap[LAError.biometryNotAvailable.rawValue] ?? ""
|
||||
}
|
||||
} else if !available, let error = error {
|
||||
reason = error.localizedDescription
|
||||
if let failureReason = error.localizedFailureReason {
|
||||
reason = "\(reason ?? ""): \(failureReason)"
|
||||
}
|
||||
errorCode =
|
||||
authenticationErrorCodeMap[error.code] ?? authenticationErrorCodeMap[
|
||||
LAError.biometryNotAvailable.rawValue] ?? ""
|
||||
}
|
||||
|
||||
self.status = BiometricStatus(
|
||||
available: available,
|
||||
biometryType: context.biometryType,
|
||||
errorReason: reason,
|
||||
errorCode: errorCode
|
||||
)
|
||||
}
|
||||
|
||||
@objc func status(_ invoke: Invoke) {
|
||||
if self.status.available {
|
||||
invoke.resolve([
|
||||
"isAvailable": self.status.available,
|
||||
"biometryType": self.status.biometryType.rawValue,
|
||||
])
|
||||
} else {
|
||||
invoke.resolve([
|
||||
"isAvailable": self.status.available,
|
||||
"biometryType": self.status.biometryType.rawValue,
|
||||
"error": self.status.errorReason ?? "",
|
||||
"errorCode": self.status.errorCode ?? "",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@objc func authenticate(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(AuthOptions.self)
|
||||
|
||||
let allowDeviceCredential = args.allowDeviceCredential ?? false
|
||||
|
||||
guard self.status.available || allowDeviceCredential else {
|
||||
// Biometry unavailable, fallback disabled
|
||||
invoke.reject(
|
||||
self.status.errorReason ?? "",
|
||||
code: self.status.errorCode ?? ""
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let context = LAContext()
|
||||
context.localizedFallbackTitle = args.fallbackTitle
|
||||
context.localizedCancelTitle = args.cancelTitle
|
||||
context.touchIDAuthenticationAllowableReuseDuration = 0
|
||||
|
||||
// force system default fallback title if an empty string is provided (the OS hides the fallback button in this case)
|
||||
if allowDeviceCredential,
|
||||
let fallbackTitle = context.localizedFallbackTitle,
|
||||
fallbackTitle.isEmpty
|
||||
{
|
||||
context.localizedFallbackTitle = nil
|
||||
}
|
||||
|
||||
context.evaluatePolicy(
|
||||
allowDeviceCredential
|
||||
? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics,
|
||||
localizedReason: args.reason
|
||||
) { success, error in
|
||||
if success {
|
||||
invoke.resolve()
|
||||
} else {
|
||||
if let policyError = error as? LAError {
|
||||
let code = self.authenticationErrorCodeMap[policyError.code.rawValue]
|
||||
invoke.reject(policyError.localizedDescription, code: code)
|
||||
} else {
|
||||
invoke.reject(
|
||||
"Unknown error",
|
||||
code: self.authenticationErrorCodeMap[LAError.authenticationFailed.rawValue]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_biometric")
|
||||
func initPlugin() -> Plugin {
|
||||
return BiometricPlugin()
|
||||
}
|
||||
Reference in New Issue
Block a user