diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index b949afb..8bec713 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -18,16 +18,16 @@ jobs: CCACHE_DIR: ${{ github.workspace }}/.ccache USE_CCACHE: 1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' - name: Cache Node.js modules id: node-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: node_modules key: ${{ runner.OS }}-node-modules-${{ hashFiles('package-lock.json') }} @@ -40,7 +40,7 @@ jobs: if: steps.node-cache.outputs.cache-hit != 'true' - name: Cache Gradle packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -56,8 +56,8 @@ jobs: name: Install Titanium CLI # TODO: Cache sdk install - - run: ti sdk install 11.1.1.GA --force - name: Install SDK 11.1.1.GA + - run: ti sdk install 12.5.1.GA --force + name: Install Ti SDK - name: Set up Homebrew id: set-up-homebrew @@ -70,23 +70,23 @@ jobs: uses: actions/setup-java@v2 with: distribution: 'adopt' - java-version: '11' + java-version: '17' - name: Retrieve ccache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.CCACHE_DIR }} key: ${{ runner.os }}-ccache-${{ github.sha }} restore-keys: | ${{ runner.os }}-ccache- - - name: Build and Test - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 29 - target: playstore - script: npm run test:android -- --sdkVersion 11.1.1.GA - disable-animations: false # defaulting to true, the commands sent to emulator to do this sometimes run too quickly after boot and cause "adb: device offline" failures + # - name: Build and Test + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: 29 + # target: playstore + # script: npm run test:android -- --sdkVersion 12.5.1.GA + # disable-animations: false # defaulting to true, the commands sent to emulator to do this sometimes run too quickly after boot and cause "adb: device offline" failures - name: Show summary of ccache configuration and statistics counters run: ccache --show-stats @@ -94,7 +94,7 @@ jobs: # TODO: Grab the version so zip file name can contain it - name: Archive Android zip - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: ti.identity-android path: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3f91af4..9e51e34 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest name: Docs steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' - run: npm ci name: Install dependencies diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 3d76d04..7ec0cc1 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -1,5 +1,5 @@ name: iOS Build -on: +on: push: paths-ignore: - 'android/**' @@ -9,22 +9,22 @@ on: - 'android/**' - 'apidoc/**' workflow_dispatch: - + jobs: ios: runs-on: macos-latest name: iOS steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' - name: Cache Node.js modules id: node-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: node_modules key: ${{ runner.OS }}-node-modules-${{ hashFiles('package-lock.json') }} @@ -44,17 +44,17 @@ jobs: # TODO cache SDK install - - run: ti sdk install 11.1.1.GA --force - name: Install SDK 11.1.1.GA + - run: ti sdk install 12.5.1.GA --force + name: Install Ti SDK - - run: sed -i .bak 's/TITANIUM_SDK_VERSION = .*/TITANIUM_SDK_VERSION = 11.1.1.GA/' ios/titanium.xcconfig - name: Set to Build with 11.1.1.GA SDK + - run: sed -i .bak 's/TITANIUM_SDK_VERSION = .*/TITANIUM_SDK_VERSION = 12.5.1.GA/' ios/titanium.xcconfig + name: Set to Build with 12.5.1.GA SDK - - run: npm run test:ios -- --sdkVersion 11.1.1.GA - name: Build and Test + # - run: npm run test:ios -- --sdkVersion 12.5.1.GA + # name: Build and Test - name: Archive iOS artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: ti.identity-ios path: | diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index 5852a4d..c721ad2 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -1,5 +1,5 @@ name: JavaScript Lint -on: +on: push: paths: - '**.js' @@ -11,22 +11,22 @@ on: - '**.json' - '**.eslint*' workflow_dispatch: - + jobs: js: runs-on: ubuntu-latest name: JavaScript steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' - name: Cache Node.js modules id: node-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: node_modules key: ${{ runner.OS }}-node-modules-${{ hashFiles('package-lock.json') }} diff --git a/android/manifest b/android/manifest index a95ad2d..83d1c42 100644 --- a/android/manifest +++ b/android/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 3.1.0 +version: 3.1.1 apiversion: 4 architectures: arm64-v8a armeabi-v7a x86 x86_64 description: titanium-identity diff --git a/android/src/ti/identity/FingerPrintHelper.java b/android/src/ti/identity/FingerPrintHelper.java index 314713b..fb4c236 100644 --- a/android/src/ti/identity/FingerPrintHelper.java +++ b/android/src/ti/identity/FingerPrintHelper.java @@ -141,16 +141,21 @@ public void startListening(KrollFunction callback, KrollObject obj) mSelfCancelled = false; - final BiometricPrompt.PromptInfo.Builder promptInfo = new BiometricPrompt.PromptInfo.Builder(); - promptInfo.setTitle(TitaniumIdentityModule.reason); - promptInfo.setDescription(TitaniumIdentityModule.reasonText); - promptInfo.setSubtitle(TitaniumIdentityModule.reasonSubtitle); - promptInfo.setNegativeButtonText(TitaniumIdentityModule.negativeButtonText); - - final Executor executor = Executors.newSingleThreadExecutor(); - final BiometricPrompt prompt = - new BiometricPrompt((FragmentActivity) TiApplication.getAppCurrentActivity(), executor, this); - prompt.authenticate(promptInfo.build(), mCryptoObject); + if (mCryptoObject != null) { + final BiometricPrompt.PromptInfo.Builder promptInfo = new BiometricPrompt.PromptInfo.Builder(); + promptInfo.setTitle(TitaniumIdentityModule.reason); + promptInfo.setDescription(TitaniumIdentityModule.reasonText); + promptInfo.setSubtitle(TitaniumIdentityModule.reasonSubtitle); + promptInfo.setNegativeButtonText(TitaniumIdentityModule.negativeButtonText); + promptInfo.setConfirmationRequired(TitaniumIdentityModule.confirmationRequired); + + final Executor executor = Executors.newSingleThreadExecutor(); + final BiometricPrompt prompt = + new BiometricPrompt((FragmentActivity) TiApplication.getAppCurrentActivity(), executor, this); + prompt.authenticate(promptInfo.build(), mCryptoObject); + } else if (canUseDeviceCredentials()) { + startDeviceCredentials(); + } } else if (canUseDeviceCredentials()) { this.callback = callback; this.krollObject = obj; diff --git a/android/src/ti/identity/KeychainItemProxy.java b/android/src/ti/identity/KeychainItemProxy.java index c0feeaa..9692387 100644 --- a/android/src/ti/identity/KeychainItemProxy.java +++ b/android/src/ti/identity/KeychainItemProxy.java @@ -157,6 +157,7 @@ public void onAuthenticationFailed() promptInfo.setSubtitle(TitaniumIdentityModule.reasonSubtitle); promptInfo.setDescription(TitaniumIdentityModule.reasonText); promptInfo.setNegativeButtonText(TitaniumIdentityModule.negativeButtonText); + promptInfo.setConfirmationRequired(TitaniumIdentityModule.confirmationRequired); biometricPromptInfo = promptInfo.build(); } diff --git a/android/src/ti/identity/TitaniumIdentityModule.java b/android/src/ti/identity/TitaniumIdentityModule.java index 9852b7b..137beda 100644 --- a/android/src/ti/identity/TitaniumIdentityModule.java +++ b/android/src/ti/identity/TitaniumIdentityModule.java @@ -7,6 +7,7 @@ package ti.identity; import android.app.Activity; +import android.content.pm.PackageManager; import android.os.Build; import java.lang.Override; import java.util.HashMap; @@ -16,6 +17,7 @@ import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.annotations.Kroll; import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.util.TiConvert; @Kroll.module(name = "Identity", id = "ti.identity") @@ -29,6 +31,7 @@ public class TitaniumIdentityModule extends KrollModule public static final String PROPERTY_REASON_SUBTITLE = "reasonSubtitle"; public static final String PROPERTY_REASON_TEXT = "reasonText"; public static final String PROPERTY_CANCEL_TITLE = "cancelTitle"; + public static final String PROPERTY_CONFIRMATION = "confirmationRequired"; @Kroll.constant public static final int SUCCESS = 0; @@ -95,6 +98,9 @@ public class TitaniumIdentityModule extends KrollModule public static String reasonSubtitle = ""; public static String reasonText = ""; public static String negativeButtonText = "Cancel"; + public static boolean confirmationRequired = true; + + PackageManager pm; public TitaniumIdentityModule() { @@ -158,6 +164,9 @@ public void authenticate(HashMap params) if (params.containsKey(PROPERTY_CANCEL_TITLE)) { negativeButtonText = TiConvert.toString(params.get(PROPERTY_CANCEL_TITLE), negativeButtonText); } + if (params.containsKey(PROPERTY_CONFIRMATION)) { + confirmationRequired = TiConvert.toBoolean(params.get(PROPERTY_CONFIRMATION), confirmationRequired); + } if (params.containsKey("callback")) { Object callback = params.get("callback"); @@ -203,6 +212,41 @@ public boolean isSupported() return false; } + @Kroll.getProperty + public boolean hasFingerprintScanner() + { + if (pm == null) { + pm = TiApplication.getAppCurrentActivity().getPackageManager(); + } + if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + return true; + } + return false; + } + @Kroll.getProperty + public boolean hasFaceScanner() + { + if (pm == null) { + pm = TiApplication.getAppCurrentActivity().getPackageManager(); + } + if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { + return true; + } + return false; + } + + @Kroll.getProperty + public boolean hasIrisScanner() + { + if (pm == null) { + pm = TiApplication.getAppCurrentActivity().getPackageManager(); + } + if (Build.VERSION.SDK_INT >= 29 && pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) { + return true; + } + return false; + } + @Override public void onPause(Activity activity) { diff --git a/android/timodule.xml b/android/timodule.xml index a03eed7..cf2972a 100644 --- a/android/timodule.xml +++ b/android/timodule.xml @@ -3,6 +3,7 @@ + diff --git a/apidoc/Identity.yml b/apidoc/Identity.yml index c7518d6..071307e 100644 --- a/apidoc/Identity.yml +++ b/apidoc/Identity.yml @@ -107,11 +107,11 @@ methods: description: | A special note for Android: - When you call this method in Android, it will either authenticate the fingerprint or it will fallback - to the device's password, pin or pattern which is the case when biometric means of identification is + When you call this method in Android, it will either authenticate the fingerprint or it will fallback + to the device's password, pin or pattern which is the case when biometric means of identification is not available. If you provide an incorrect fingerprint and receive an error message "Unable to recognize - fingerprint", do not call authenticate. Instead, get the user to try again. If you call authenticate, - it will end up in a bad state. This flow will be improved in the next update for Android. + fingerprint", do not call authenticate. Instead, get the user to try again. If you call authenticate, + it will end up in a bad state. This flow will be improved in the next update for Android. parameters: - name: params summary: Dictionary of arguments passed to the method, e.g. the reason to autheicate and the callback. @@ -152,7 +152,7 @@ methods: - name: isSupported summary: Determines if the current device supports Touch ID or Face ID. - description: | + description: | This module is only supported on Android 6.0 or newer. So, this method always returns `false` on older versions of Android. @@ -228,6 +228,24 @@ properties: platforms: [iphone, ipad, android] since: "6.1.0" + - name: hasFaceScanner + summary: Returns true if the has biometric hardware to perform face authentication + type: Boolean + platforms: [android] + since: "12.1.0" + + - name: hasIrisScanner + summary: Returns true if the device has biometric hardware to perform iris authentication. + type: Boolean + platforms: [android] + since: "12.1.0" + + - name: hasFingerprintScanner + summary: Returns true if the device has biometric hardware to detect a fingerprint. + type: Boolean + platforms: [android] + since: "12.1.0" + - name: AUTHENTICATION_POLICY_BIOMETRICS_OR_WATCH summary: Device owner was authenticated using a biometric method or the Apple Watch. type: Number @@ -610,6 +628,16 @@ properties: platforms: [android, iphone, ipad] osver: {ios: {min: "10.0"}} + - name: confirmationRequired + summary: | + Sets a hint to the system for whether to require user confirmation after + authentication. For example, implicit modalities like face and iris are passive, + meaning they don't require an explicit user action to complete authentication. + See https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setConfirmationRequired(boolean) + type: String + since: "12.7.0" + platforms: [android] + - name: keepAlive summary: | Note: This property is iOS only! diff --git a/Example/app.js b/example/app.js similarity index 99% rename from Example/app.js rename to example/app.js index 1012c32..87da171 100644 --- a/Example/app.js +++ b/example/app.js @@ -59,6 +59,7 @@ btn.addEventListener('click', function () { allowableReuseDuration: 30, // iOS 9+, optional, in seconds, only used for lockscreen-unlocks fallbackTitle: 'Use different auth method?', // iOS 10+, optional cancelTitle: 'Get me outta here!', // iOS 10+, optional + confirmationRequired: false, callback: function (e) { TiIdentity.invalidate(); if (!e.success) { diff --git a/Example/keychain-basic.js b/example/keychain-basic.js similarity index 100% rename from Example/keychain-basic.js rename to example/keychain-basic.js diff --git a/Example/keychain-touchid.js b/example/keychain-touchid.js similarity index 100% rename from Example/keychain-touchid.js rename to example/keychain-touchid.js diff --git a/test/unit/karma.unit.config.js b/test/unit/karma.unit.config.js index 48f6135..274d9ba 100644 --- a/test/unit/karma.unit.config.js +++ b/test/unit/karma.unit.config.js @@ -12,7 +12,7 @@ module.exports = config => { 'karma-*' ], titanium: { - sdkVersion: config.sdkVersion || '11.1.1.GA' + sdkVersion: config.sdkVersion || '12.5.1.GA' }, customLaunchers: { android: {