@capgo/capacitor-native-biometric 8.3.6 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -5
- package/android/src/main/java/ee/forgr/biometric/AuthActivity.java +164 -6
- package/android/src/main/java/ee/forgr/biometric/NativeBiometric.java +93 -5
- package/dist/docs.json +128 -0
- package/dist/esm/definitions.d.ts +73 -0
- package/dist/esm/definitions.js +20 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +8 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +28 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +28 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NativeBiometricPlugin/NativeBiometricPlugin.swift +133 -11
- package/package.json +3 -2
|
@@ -11,7 +11,7 @@ import LocalAuthentication
|
|
|
11
11
|
|
|
12
12
|
@objc(NativeBiometricPlugin)
|
|
13
13
|
public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
14
|
-
private let pluginVersion: String = "8.
|
|
14
|
+
private let pluginVersion: String = "8.4.0"
|
|
15
15
|
public let identifier = "NativeBiometricPlugin"
|
|
16
16
|
public let jsName = "NativeBiometric"
|
|
17
17
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -20,6 +20,7 @@ public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
20
20
|
CAPPluginMethod(name: "getCredentials", returnType: CAPPluginReturnPromise),
|
|
21
21
|
CAPPluginMethod(name: "setCredentials", returnType: CAPPluginReturnPromise),
|
|
22
22
|
CAPPluginMethod(name: "deleteCredentials", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "getSecureCredentials", returnType: CAPPluginReturnPromise),
|
|
23
24
|
CAPPluginMethod(name: "isCredentialsSaved", returnType: CAPPluginReturnPromise),
|
|
24
25
|
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
25
26
|
]
|
|
@@ -190,20 +191,95 @@ public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
190
191
|
return
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
let accessControl = call.getInt("accessControl") ?? 0
|
|
193
195
|
let credentials = Credentials(username: username, password: password)
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
try storeCredentialsInKeychain(credentials, server)
|
|
197
|
-
call.resolve()
|
|
198
|
-
} catch KeychainError.duplicateItem {
|
|
197
|
+
if accessControl > 0 {
|
|
199
198
|
do {
|
|
200
|
-
try
|
|
199
|
+
try storeProtectedCredentials(credentials, server, accessControl)
|
|
201
200
|
call.resolve()
|
|
201
|
+
} catch KeychainError.duplicateItem {
|
|
202
|
+
do {
|
|
203
|
+
try deleteProtectedCredentials(server)
|
|
204
|
+
try storeProtectedCredentials(credentials, server, accessControl)
|
|
205
|
+
call.resolve()
|
|
206
|
+
} catch {
|
|
207
|
+
call.reject(error.localizedDescription)
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
call.reject(error.localizedDescription)
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
do {
|
|
214
|
+
try storeCredentialsInKeychain(credentials, server)
|
|
215
|
+
call.resolve()
|
|
216
|
+
} catch KeychainError.duplicateItem {
|
|
217
|
+
do {
|
|
218
|
+
try updateCredentialsInKeychain(credentials, server)
|
|
219
|
+
call.resolve()
|
|
220
|
+
} catch {
|
|
221
|
+
call.reject(error.localizedDescription)
|
|
222
|
+
}
|
|
202
223
|
} catch {
|
|
203
224
|
call.reject(error.localizedDescription)
|
|
204
225
|
}
|
|
205
|
-
}
|
|
206
|
-
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@objc func getSecureCredentials(_ call: CAPPluginCall) {
|
|
230
|
+
guard let server = call.getString("server") else {
|
|
231
|
+
call.reject("No server name was provided")
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let context = LAContext()
|
|
236
|
+
if let reason = call.getString("reason") {
|
|
237
|
+
context.localizedReason = reason
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let query: [String: Any] = [
|
|
241
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
242
|
+
kSecAttrService as String: server,
|
|
243
|
+
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
244
|
+
kSecReturnAttributes as String: true,
|
|
245
|
+
kSecReturnData as String: true,
|
|
246
|
+
kSecUseAuthenticationContext as String: context
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
250
|
+
var item: CFTypeRef?
|
|
251
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
252
|
+
|
|
253
|
+
DispatchQueue.main.async {
|
|
254
|
+
if status == errSecUserCanceled {
|
|
255
|
+
call.reject("User canceled biometric authentication", "16")
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
guard status == errSecSuccess else {
|
|
259
|
+
if status == errSecItemNotFound {
|
|
260
|
+
call.reject("No protected credentials found for server", "21")
|
|
261
|
+
} else if status == errSecAuthFailed {
|
|
262
|
+
call.reject("Biometric authentication failed", "10")
|
|
263
|
+
} else {
|
|
264
|
+
call.reject("Failed to retrieve credentials: \(status)", "0")
|
|
265
|
+
}
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
guard let existingItem = item as? [String: Any],
|
|
270
|
+
let passwordData = existingItem[kSecValueData as String] as? Data,
|
|
271
|
+
let password = String(data: passwordData, encoding: .utf8),
|
|
272
|
+
let username = existingItem[kSecAttrAccount as String] as? String
|
|
273
|
+
else {
|
|
274
|
+
call.reject("Unexpected credential data")
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
var obj = JSObject()
|
|
279
|
+
obj["username"] = username
|
|
280
|
+
obj["password"] = password
|
|
281
|
+
call.resolve(obj)
|
|
282
|
+
}
|
|
207
283
|
}
|
|
208
284
|
}
|
|
209
285
|
|
|
@@ -215,6 +291,7 @@ public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
215
291
|
|
|
216
292
|
do {
|
|
217
293
|
try deleteCredentialsFromKeychain(server)
|
|
294
|
+
try deleteProtectedCredentials(server)
|
|
218
295
|
call.resolve()
|
|
219
296
|
} catch {
|
|
220
297
|
call.reject(error.localizedDescription)
|
|
@@ -228,11 +305,10 @@ public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
228
305
|
}
|
|
229
306
|
|
|
230
307
|
var obj = JSObject()
|
|
231
|
-
obj["isSaved"] = checkCredentialsExist(server)
|
|
308
|
+
obj["isSaved"] = checkCredentialsExist(server) || checkProtectedCredentialsExist(server)
|
|
232
309
|
call.resolve(obj)
|
|
233
310
|
}
|
|
234
311
|
|
|
235
|
-
// Check if credentials exist in Keychain
|
|
236
312
|
func checkCredentialsExist(_ server: String) -> Bool {
|
|
237
313
|
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
|
|
238
314
|
kSecAttrServer as String: server,
|
|
@@ -242,7 +318,53 @@ public class NativeBiometricPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
242
318
|
return status == errSecSuccess
|
|
243
319
|
}
|
|
244
320
|
|
|
245
|
-
|
|
321
|
+
func checkProtectedCredentialsExist(_ server: String) -> Bool {
|
|
322
|
+
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
|
323
|
+
kSecAttrService as String: server,
|
|
324
|
+
kSecMatchLimit as String: kSecMatchLimitOne]
|
|
325
|
+
|
|
326
|
+
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
|
327
|
+
return status == errSecSuccess
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
func storeProtectedCredentials(_ credentials: Credentials, _ server: String, _ accessControl: Int) throws {
|
|
331
|
+
guard let passwordData = credentials.password.data(using: .utf8) else {
|
|
332
|
+
throw KeychainError.unexpectedPasswordData
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let flags: SecAccessControlCreateFlags = accessControl == 1 ? .biometryCurrentSet : .biometryAny
|
|
336
|
+
guard let access = SecAccessControlCreateWithFlags(
|
|
337
|
+
kCFAllocatorDefault,
|
|
338
|
+
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
|
|
339
|
+
flags,
|
|
340
|
+
nil
|
|
341
|
+
) else {
|
|
342
|
+
throw KeychainError.unhandledError(status: errSecParam)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let query: [String: Any] = [
|
|
346
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
347
|
+
kSecAttrService as String: server,
|
|
348
|
+
kSecAttrAccount as String: credentials.username,
|
|
349
|
+
kSecValueData as String: passwordData,
|
|
350
|
+
kSecAttrAccessControl as String: access
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
354
|
+
guard status != errSecDuplicateItem else { throw KeychainError.duplicateItem }
|
|
355
|
+
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
func deleteProtectedCredentials(_ server: String) throws {
|
|
359
|
+
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
|
360
|
+
kSecAttrService as String: server]
|
|
361
|
+
|
|
362
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
363
|
+
guard status == errSecSuccess || status == errSecItemNotFound else {
|
|
364
|
+
throw KeychainError.unhandledError(status: status)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
246
368
|
func storeCredentialsInKeychain(_ credentials: Credentials, _ server: String) throws {
|
|
247
369
|
guard let passwordData = credentials.password.data(using: .utf8) else {
|
|
248
370
|
throw KeychainError.unexpectedPasswordData
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-native-biometric",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "This plugin gives access to the native biometric apis for android and iOS",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
48
48
|
"clean": "rimraf ./dist",
|
|
49
49
|
"watch": "tsc --watch",
|
|
50
|
-
"prepublishOnly": "npm run build"
|
|
50
|
+
"prepublishOnly": "npm run build",
|
|
51
|
+
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@capacitor/android": "^8.0.0",
|