From 7f910323fc6ac6fe5f5dfdc92f616165e3e8a9a7 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 10 Oct 2023 13:54:24 -0300 Subject: [PATCH] feat: implement iOS --- .../src-tauri/gen/apple/api_iOS/Info.plist | 2 + .../ios/Sources/BiometricPlugin.swift | 133 +++++++++++++++++- pnpm-lock.yaml | 93 ++++++------ 3 files changed, 183 insertions(+), 45 deletions(-) diff --git a/examples/api/src-tauri/gen/apple/api_iOS/Info.plist b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist index f0ac4d62..d37dab96 100644 --- a/examples/api/src-tauri/gen/apple/api_iOS/Info.plist +++ b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist @@ -40,5 +40,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSFaceIDUsageDescription + Biometric Test diff --git a/plugins/biometric/ios/Sources/BiometricPlugin.swift b/plugins/biometric/ios/Sources/BiometricPlugin.swift index 8ffadd23..ff3113f9 100644 --- a/plugins/biometric/ios/Sources/BiometricPlugin.swift +++ b/plugins/biometric/ios/Sources/BiometricPlugin.swift @@ -2,15 +2,142 @@ // 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 + } +} + class BiometricPlugin: Plugin { - @objc public func ping(_ invoke: Invoke) throws { - let value = invoke.getString("value") - invoke.resolve(["value": value as Any]) + 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 { + 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, + "reason": self.status.errorReason ?? "", + "code": self.status.errorCode ?? "", + ]) + } + } + + @objc func authenticate(_ invoke: Invoke) { + guard self.status.available else { + invoke.reject( + self.status.errorReason ?? "", + self.status.errorCode ?? "" + ) + return + } + + guard let reason = invoke.getString("reason"), !reason.isEmpty else { + invoke.reject("`reason` is required") + return + } + + let context = LAContext() + context.localizedFallbackTitle = invoke.getString("fallbackTitle") + context.localizedCancelTitle = invoke.getString("cancelTitle") + context.touchIDAuthenticationAllowableReuseDuration = 0 + + let allowDeviceCredential = invoke.getBool("allowDeviceCredential") ?? false + + // 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: 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) + } else { + invoke.reject( + "Unknown error", + self.authenticationErrorCodeMap[LAError.authenticationFailed.rawValue] + ) + } + } + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e34286e3..01d263bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,7 +138,7 @@ importers: version: 3.59.1 unocss: specifier: ^0.53.1 - version: 0.53.1(postcss@8.4.26)(rollup@3.26.3)(vite@4.4.4) + version: 0.53.1(postcss@8.4.31)(rollup@3.26.3)(vite@4.4.4) vite: specifier: ^4.3.9 version: 4.4.4 @@ -1370,8 +1370,8 @@ packages: engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} dev: false - /@tauri-apps/cli-darwin-arm64@2.0.0-alpha.14: - resolution: {integrity: sha512-3K416rvSUt8el/fdPnSnHJOI2j5Os9Kyy17XZp+z3PKRRuo/iJPp9L3w0zFGYsh7C+ylzV4OBUSVTi+e+gO5qA==} + /@tauri-apps/cli-darwin-arm64@2.0.0-alpha.15: + resolution: {integrity: sha512-PxmXanPZtSLtDJyEoj538//cauKoyc/sExAO0fTwJ+o8y2NZB/qQfpbdMIloQQnusRwh+6RjOr0Zs5Y6nBhO5Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1379,8 +1379,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-darwin-x64@2.0.0-alpha.14: - resolution: {integrity: sha512-aLEUGG8Z0UpTENe4/UG6DU8bnB2e1uxyxYvcmFKrHv+EAtR9nLH14alBxPl2K54YXy3JLR4bKROW15a/sFrX9g==} + /@tauri-apps/cli-darwin-x64@2.0.0-alpha.15: + resolution: {integrity: sha512-IcJGd6mIwQQ9xQhmkNHWjERJoGYpZEknhWeU8a2MnuosX8c9O/zmKWey4ol2KPrumMdmbh8QZzPyh9986GmnUA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1388,8 +1388,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.14: - resolution: {integrity: sha512-Lu7unNvurBccxfHIaUQ0gPgUioTkQBMtWGrqO/auZ/JbjPR1W2eBlRwVNXf+nBWX9HwomPR3YD5yZuZmzxRV2g==} + /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.15: + resolution: {integrity: sha512-zD88WJaEZ49BzgmIgj0RVFF/zKrB+NYHEDwmvt60eehQCPfnMuE5/asj0Gp4YJRZ07jZzDfzMCdTGbwWsLnZEA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -1397,8 +1397,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.14: - resolution: {integrity: sha512-g8HkwKvAsWLLMJzPup7B1BCilYmXKwXdee7sf8QFbaIUSccR8i5pXLK5N/quKw5lmldYgFveEyuW9Qs8RgTYnQ==} + /@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.15: + resolution: {integrity: sha512-rmHIZsEb1RT5Ny4hjeK7LC3MRqWLZBfiKC29DX5UzhJySb9g0UeR2esx1PMX6kuU8DOC0RBr8xpEmoTNMtFJ3Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1406,8 +1406,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.14: - resolution: {integrity: sha512-ag4UuX6zg7vmBFWmg9ChyiJI7GTMkc8tjr/qobd3Lg9ddmjnVWwLUHt6v1kYhXiU7iLPD5DYDIjU8x/POc3hSA==} + /@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.15: + resolution: {integrity: sha512-l3oix62YRE/vjpdxWq38NwZ61yg1vCcGAdfHaSt+Um/ojZHudekchQx56sEh7IMxsHxEtipxZdNEb1WsyAa5JA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1415,8 +1415,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.14: - resolution: {integrity: sha512-+CviROc4fzrGqqyHQXh3uc2dGr/oYr19I8r2k+LJ2CDfmtj7CbNd/oC5oehHbHdw1oGFKuDPudrTGvzdRNygYA==} + /@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.15: + resolution: {integrity: sha512-m7jWcyA4URtfvM4ySN1G3mO6gQP0qULawP9henks/bcrx2DU5xFP7WFxUxQhlWEtjwtJOI/NscQfzUEE6igs+Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1424,8 +1424,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.14: - resolution: {integrity: sha512-aCP51HOAQXgVhyPHXKy627bYVRkNnpCvSU3L03pYV8YDoGo+veeuek5UiW7PlNdwx52B/yC3Jz7Dr3gEbFimfQ==} + /@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.15: + resolution: {integrity: sha512-Xa4JTnYbebnLAMY7JdNuUDgnv/wWA4a8fbg1288kckq3aRXb+ETTV3Tlr/rnsx1s3TpECmSkXjTIvNecvsqjTA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1433,8 +1433,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-arm64-msvc@2.0.0-alpha.14: - resolution: {integrity: sha512-b6Ei5ERUF0KS1bttM7i6U62GmjIvlgK03XZqvL/KLNvUfqRMu8F7JA1ejSExgTxhEhKSWA768HiTXpXk2GjFFw==} + /@tauri-apps/cli-win32-arm64-msvc@2.0.0-alpha.15: + resolution: {integrity: sha512-YOKmqenjwQkwBesJ3rYWnJ2renRhPAWIdIoTRhMnDacRk28mMWizsGWLT7ZDbYi7AHMR6jMk0eYgAKKd+uBjFg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1442,8 +1442,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.14: - resolution: {integrity: sha512-TDkvu5pd37bKxZ6N+BqngCNGcefY7aHxyJ3BdBGxF+wRMjEMh70mgEXk8i0uM/aUi/Kl1GQoO6xJfUDlIMPXOA==} + /@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.15: + resolution: {integrity: sha512-Z8yMn6jKSCN8atdWwIzHNoqd+kS684RpgFoZhftcxtqYDXfSgU63KLasKu2Wu12a/7TmXqHGHlEBst9nD2VSLw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1451,8 +1451,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.14: - resolution: {integrity: sha512-9yfoEe2RSykKr5hCifVAL5o0gHXgRCS+Wo+RJjQ9L2+QHY7XPLZYAhj/h8jdcAdRveyIQwat3k7wl+SW87v1eg==} + /@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.15: + resolution: {integrity: sha512-fcIXUgI1PKeAj2cp7vvXDssWcXxhauCyvtJPmaCVl5pk+5aJlOSx5TPjv0BRyaIO8l4IPW1IakvTRcDvEAQHRw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1461,20 +1461,20 @@ packages: optional: true /@tauri-apps/cli@2.0.0-alpha.15: - resolution: {integrity: sha512-4/IQwN5S94D6LTXQrDWbSea0pGb9TTC4BwxHUFmhep4NjFxms161v1zadAUIsq/N2x6WwCBGrsdq9SIkgKv49Q==, tarball: https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-alpha.15.tgz} + resolution: {integrity: sha512-eMMD5MXJDt/j37IGBP501Ov3lux+mrA1WT4EjTk+Oaw4t8fb8ncb7yvbVZ6qyzVo7WHplIGKRzyV0CyZXZropQ==} engines: {node: '>= 10'} hasBin: true optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.0.0-alpha.14 - '@tauri-apps/cli-darwin-x64': 2.0.0-alpha.14 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-alpha.14 - '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-alpha.14 - '@tauri-apps/cli-linux-arm64-musl': 2.0.0-alpha.14 - '@tauri-apps/cli-linux-x64-gnu': 2.0.0-alpha.14 - '@tauri-apps/cli-linux-x64-musl': 2.0.0-alpha.14 - '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-alpha.14 - '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-alpha.14 - '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.14 + '@tauri-apps/cli-darwin-arm64': 2.0.0-alpha.15 + '@tauri-apps/cli-darwin-x64': 2.0.0-alpha.15 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-alpha.15 + '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-alpha.15 + '@tauri-apps/cli-linux-arm64-musl': 2.0.0-alpha.15 + '@tauri-apps/cli-linux-x64-gnu': 2.0.0-alpha.15 + '@tauri-apps/cli-linux-x64-musl': 2.0.0-alpha.15 + '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-alpha.15 + '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-alpha.15 + '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.15 dev: true /@tauri-apps/toml@2.2.4: @@ -1775,7 +1775,7 @@ packages: sirv: 2.0.3 dev: true - /@unocss/postcss@0.53.1(postcss@8.4.26): + /@unocss/postcss@0.53.1(postcss@8.4.31): resolution: {integrity: sha512-vuUj/Tsvn6/YlEYp/AezyjoZLNBp+YomwpQctNZAC5ged5cqKfaw+oISw1LYzi/48Ynx7cV/4XqikApuozrvRQ==} engines: {node: '>=14'} peerDependencies: @@ -1786,7 +1786,7 @@ packages: css-tree: 2.3.1 fast-glob: 3.3.0 magic-string: 0.30.1 - postcss: 8.4.26 + postcss: 8.4.31 dev: true /@unocss/preset-attributify@0.53.1: @@ -2185,7 +2185,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /cidr-regex@4.0.3: @@ -3043,8 +3043,8 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -4103,6 +4103,15 @@ packages: source-map-js: 1.0.2 dev: true + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4236,7 +4245,7 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /run-async@2.4.1: @@ -4841,7 +4850,7 @@ packages: '@types/unist': 2.0.7 dev: true - /unocss@0.53.1(postcss@8.4.26)(rollup@3.26.3)(vite@4.4.4): + /unocss@0.53.1(postcss@8.4.31)(rollup@3.26.3)(vite@4.4.4): resolution: {integrity: sha512-0lRblA8hX7VUu5dywbcStzm590Iz5ahSJGsMNKNH3+u9C7AfJcKT8epxjkIkJWQBNJLD5vsao4SuuhLWB7eMQQ==} engines: {node: '>=14'} peerDependencies: @@ -4854,7 +4863,7 @@ packages: '@unocss/cli': 0.53.1(rollup@3.26.3) '@unocss/core': 0.53.1 '@unocss/extractor-arbitrary-variants': 0.53.1 - '@unocss/postcss': 0.53.1(postcss@8.4.26) + '@unocss/postcss': 0.53.1(postcss@8.4.31) '@unocss/preset-attributify': 0.53.1 '@unocss/preset-icons': 0.53.1 '@unocss/preset-mini': 0.53.1 @@ -4932,7 +4941,7 @@ packages: postcss: 8.4.26 rollup: 3.26.3 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /vite@4.4.4: @@ -4967,7 +4976,7 @@ packages: postcss: 8.4.26 rollup: 3.26.3 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /vitefu@0.2.4(vite@4.4.4):