@capawesome/capacitor-passkeys 0.0.1
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/CapawesomeCapacitorPasskeys.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +472 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/Passkeys.java +173 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/PasskeysPlugin.java +115 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/CustomExceptions.java +12 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/options/CreatePasskeyOptions.java +38 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/options/GetPasskeyOptions.java +33 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/CreatePasskeyResult.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/GetPasskeyResult.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/IsAvailableResult.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1091 -0
- package/dist/esm/definitions.d.ts +567 -0
- package/dist/esm/definitions.js +47 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +18 -0
- package/dist/esm/web.js +184 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +244 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +247 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/Options/CreatePasskeyOptions.swift +81 -0
- package/ios/Plugin/Classes/Options/GetPasskeyOptions.swift +54 -0
- package/ios/Plugin/Classes/Results/CreatePasskeyResult.swift +28 -0
- package/ios/Plugin/Classes/Results/GetPasskeyResult.swift +34 -0
- package/ios/Plugin/Classes/Results/IsAvailableResult.swift +16 -0
- package/ios/Plugin/Enums/CustomError.swift +65 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Passkeys.swift +118 -0
- package/ios/Plugin/PasskeysHelper.swift +20 -0
- package/ios/Plugin/PasskeysPlugin.swift +86 -0
- package/ios/Plugin/Protocols/Result.swift +5 -0
- package/package.json +91 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import AuthenticationServices
|
|
3
|
+
|
|
4
|
+
@objc public class Passkeys: NSObject {
|
|
5
|
+
|
|
6
|
+
private var authorizationController: ASAuthorizationController?
|
|
7
|
+
private var createPasskeyCompletion: ((_ result: CreatePasskeyResult?, _ error: Error?) -> Void)?
|
|
8
|
+
private var getPasskeyCompletion: ((_ result: GetPasskeyResult?, _ error: Error?) -> Void)?
|
|
9
|
+
|
|
10
|
+
@objc public func createPasskey(_ options: CreatePasskeyOptions, presentationContextProvider: ASAuthorizationControllerPresentationContextProviding, completion: @escaping (_ result: CreatePasskeyResult?, _ error: Error?) -> Void) {
|
|
11
|
+
self.createPasskeyCompletion = completion
|
|
12
|
+
|
|
13
|
+
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.rpId)
|
|
14
|
+
let request = provider.createCredentialRegistrationRequest(challenge: options.challenge, name: options.userName, userID: options.userId)
|
|
15
|
+
if let attestation = options.attestation {
|
|
16
|
+
request.attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKind(rawValue: attestation)
|
|
17
|
+
}
|
|
18
|
+
if let userVerification = options.userVerification {
|
|
19
|
+
request.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference(rawValue: userVerification)
|
|
20
|
+
}
|
|
21
|
+
if #available(iOS 17.4, *) {
|
|
22
|
+
if !options.excludeCredentialIds.isEmpty {
|
|
23
|
+
request.excludedCredentials = options.excludeCredentialIds.map {
|
|
24
|
+
ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
performRequest(request, presentationContextProvider: presentationContextProvider)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@objc public func getPasskey(_ options: GetPasskeyOptions, presentationContextProvider: ASAuthorizationControllerPresentationContextProviding, completion: @escaping (_ result: GetPasskeyResult?, _ error: Error?) -> Void) {
|
|
33
|
+
self.getPasskeyCompletion = completion
|
|
34
|
+
|
|
35
|
+
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.rpId)
|
|
36
|
+
let request = provider.createCredentialAssertionRequest(challenge: options.challenge)
|
|
37
|
+
if !options.allowCredentialIds.isEmpty {
|
|
38
|
+
request.allowedCredentials = options.allowCredentialIds.map {
|
|
39
|
+
ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if let userVerification = options.userVerification {
|
|
43
|
+
request.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference(rawValue: userVerification)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
performRequest(request, presentationContextProvider: presentationContextProvider)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@objc public func isAvailable(completion: @escaping (_ result: IsAvailableResult?, _ error: Error?) -> Void) {
|
|
50
|
+
let result = IsAvailableResult(available: true)
|
|
51
|
+
completion(result, nil)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func mapError(_ error: Error) -> Error {
|
|
55
|
+
guard let authorizationError = error as? ASAuthorizationError else {
|
|
56
|
+
return error
|
|
57
|
+
}
|
|
58
|
+
if authorizationError.code == .canceled {
|
|
59
|
+
return CustomError.canceled
|
|
60
|
+
}
|
|
61
|
+
if error.localizedDescription.contains("is not associated with domain") {
|
|
62
|
+
return CustomError.domainNotAssociated
|
|
63
|
+
}
|
|
64
|
+
return error
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func performRequest(_ request: ASAuthorizationRequest, presentationContextProvider: ASAuthorizationControllerPresentationContextProviding) {
|
|
68
|
+
DispatchQueue.main.async {
|
|
69
|
+
let controller = ASAuthorizationController(authorizationRequests: [request])
|
|
70
|
+
controller.delegate = self
|
|
71
|
+
controller.presentationContextProvider = presentationContextProvider
|
|
72
|
+
self.authorizationController = controller
|
|
73
|
+
controller.performRequests()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
extension Passkeys: ASAuthorizationControllerDelegate {
|
|
79
|
+
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
80
|
+
authorizationController = nil
|
|
81
|
+
switch authorization.credential {
|
|
82
|
+
case let credential as ASAuthorizationPlatformPublicKeyCredentialRegistration:
|
|
83
|
+
if let attestationObject = credential.rawAttestationObject {
|
|
84
|
+
let result = CreatePasskeyResult(
|
|
85
|
+
attestationObject: attestationObject,
|
|
86
|
+
clientDataJSON: credential.rawClientDataJSON,
|
|
87
|
+
credentialId: credential.credentialID
|
|
88
|
+
)
|
|
89
|
+
createPasskeyCompletion?(result, nil)
|
|
90
|
+
} else {
|
|
91
|
+
createPasskeyCompletion?(nil, CustomError.createFailed)
|
|
92
|
+
}
|
|
93
|
+
case let credential as ASAuthorizationPlatformPublicKeyCredentialAssertion:
|
|
94
|
+
let result = GetPasskeyResult(
|
|
95
|
+
authenticatorData: credential.rawAuthenticatorData,
|
|
96
|
+
clientDataJSON: credential.rawClientDataJSON,
|
|
97
|
+
credentialId: credential.credentialID,
|
|
98
|
+
signature: credential.signature,
|
|
99
|
+
userHandle: credential.userID
|
|
100
|
+
)
|
|
101
|
+
getPasskeyCompletion?(result, nil)
|
|
102
|
+
default:
|
|
103
|
+
createPasskeyCompletion?(nil, CustomError.createFailed)
|
|
104
|
+
getPasskeyCompletion?(nil, CustomError.getFailed)
|
|
105
|
+
}
|
|
106
|
+
createPasskeyCompletion = nil
|
|
107
|
+
getPasskeyCompletion = nil
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
|
111
|
+
authorizationController = nil
|
|
112
|
+
let mappedError = mapError(error)
|
|
113
|
+
createPasskeyCompletion?(nil, mappedError)
|
|
114
|
+
getPasskeyCompletion?(nil, mappedError)
|
|
115
|
+
createPasskeyCompletion = nil
|
|
116
|
+
getPasskeyCompletion = nil
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public class PasskeysHelper {
|
|
4
|
+
public static func base64UrlFromData(_ data: Data) -> String {
|
|
5
|
+
return data.base64EncodedString()
|
|
6
|
+
.replacingOccurrences(of: "+", with: "-")
|
|
7
|
+
.replacingOccurrences(of: "/", with: "_")
|
|
8
|
+
.replacingOccurrences(of: "=", with: "")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public static func dataFromBase64Url(_ base64Url: String) -> Data? {
|
|
12
|
+
var base64 = base64Url
|
|
13
|
+
.replacingOccurrences(of: "-", with: "+")
|
|
14
|
+
.replacingOccurrences(of: "_", with: "/")
|
|
15
|
+
while base64.count % 4 != 0 {
|
|
16
|
+
base64 += "="
|
|
17
|
+
}
|
|
18
|
+
return Data(base64Encoded: base64)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import AuthenticationServices
|
|
4
|
+
|
|
5
|
+
@objc(PasskeysPlugin)
|
|
6
|
+
public class PasskeysPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
+
public let identifier = "PasskeysPlugin"
|
|
8
|
+
public let jsName = "Passkeys"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "createPasskey", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "getPasskey", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise)
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
private var implementation: Passkeys?
|
|
16
|
+
private let tag = "Passkeys"
|
|
17
|
+
|
|
18
|
+
override public func load() {
|
|
19
|
+
self.implementation = Passkeys()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objc func createPasskey(_ call: CAPPluginCall) {
|
|
23
|
+
do {
|
|
24
|
+
let options = try CreatePasskeyOptions(call)
|
|
25
|
+
|
|
26
|
+
implementation?.createPasskey(options, presentationContextProvider: self, completion: { result, error in
|
|
27
|
+
if let error = error {
|
|
28
|
+
self.rejectCall(call, error)
|
|
29
|
+
} else {
|
|
30
|
+
self.resolveCall(call, result)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
} catch {
|
|
34
|
+
rejectCall(call, error)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@objc func getPasskey(_ call: CAPPluginCall) {
|
|
39
|
+
do {
|
|
40
|
+
let options = try GetPasskeyOptions(call)
|
|
41
|
+
|
|
42
|
+
implementation?.getPasskey(options, presentationContextProvider: self, completion: { result, error in
|
|
43
|
+
if let error = error {
|
|
44
|
+
self.rejectCall(call, error)
|
|
45
|
+
} else {
|
|
46
|
+
self.resolveCall(call, result)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
} catch {
|
|
50
|
+
rejectCall(call, error)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
55
|
+
implementation?.isAvailable(completion: { result, error in
|
|
56
|
+
if let error = error {
|
|
57
|
+
self.rejectCall(call, error)
|
|
58
|
+
} else {
|
|
59
|
+
self.resolveCall(call, result)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private func rejectCall(_ call: CAPPluginCall, _ error: Error) {
|
|
65
|
+
CAPLog.print("[", self.tag, "] ", error)
|
|
66
|
+
var code: String?
|
|
67
|
+
if let customError = error as? CustomError {
|
|
68
|
+
code = customError.code
|
|
69
|
+
}
|
|
70
|
+
call.reject(error.localizedDescription, code)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func resolveCall(_ call: CAPPluginCall, _ result: Result?) {
|
|
74
|
+
if let result = result?.toJSObject() as? JSObject {
|
|
75
|
+
call.resolve(result)
|
|
76
|
+
} else {
|
|
77
|
+
call.resolve()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
extension PasskeysPlugin: ASAuthorizationControllerPresentationContextProviding {
|
|
83
|
+
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
84
|
+
return self.bridge?.webView?.window ?? ASPresentationAnchor()
|
|
85
|
+
}
|
|
86
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capawesome/capacitor-passkeys",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Capacitor plugin to create and authenticate with passkeys (WebAuthn).",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Plugin/",
|
|
14
|
+
"CapawesomeCapacitorPasskeys.podspec",
|
|
15
|
+
"Package.swift"
|
|
16
|
+
],
|
|
17
|
+
"author": "Robin Genz <mail@robingenz.dev>",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/capawesome-team/capacitor-plugins.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/capawesome-team/capacitor-plugins/issues"
|
|
25
|
+
},
|
|
26
|
+
"funding": [
|
|
27
|
+
{
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/capawesome-team/"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "opencollective",
|
|
33
|
+
"url": "https://opencollective.com/capawesome"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"homepage": "https://capawesome.io/docs/plugins/passkeys/",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"capacitor",
|
|
39
|
+
"plugin",
|
|
40
|
+
"native"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
44
|
+
"verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS && cd ..",
|
|
45
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
46
|
+
"verify:web": "npm run build",
|
|
47
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
48
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
49
|
+
"eslint": "eslint . --ext ts",
|
|
50
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
|
|
51
|
+
"swiftlint": "node-swiftlint",
|
|
52
|
+
"docgen": "docgen --api PasskeysPlugin --output-readme README.md --output-json dist/docs.json",
|
|
53
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
54
|
+
"clean": "rimraf ./dist",
|
|
55
|
+
"watch": "tsc --watch",
|
|
56
|
+
"ios:pod:install": "cd ios && pod install --repo-update && cd ..",
|
|
57
|
+
"ios:spm:install": "cd ios && swift package resolve && cd ..",
|
|
58
|
+
"prepublishOnly": "npm run build"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@capacitor/android": "8.0.0",
|
|
62
|
+
"@capacitor/cli": "8.0.0",
|
|
63
|
+
"@capacitor/core": "8.0.0",
|
|
64
|
+
"@capacitor/docgen": "0.3.1",
|
|
65
|
+
"@capacitor/ios": "8.0.0",
|
|
66
|
+
"@ionic/eslint-config": "0.4.0",
|
|
67
|
+
"eslint": "8.57.0",
|
|
68
|
+
"prettier-plugin-java": "2.6.7",
|
|
69
|
+
"rimraf": "6.1.2",
|
|
70
|
+
"rollup": "4.53.3",
|
|
71
|
+
"swiftlint": "2.0.0",
|
|
72
|
+
"typescript": "5.9.3"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@capacitor/core": ">=8.0.0"
|
|
76
|
+
},
|
|
77
|
+
"eslintConfig": {
|
|
78
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
79
|
+
},
|
|
80
|
+
"capacitor": {
|
|
81
|
+
"ios": {
|
|
82
|
+
"src": "ios"
|
|
83
|
+
},
|
|
84
|
+
"android": {
|
|
85
|
+
"src": "android"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"publishConfig": {
|
|
89
|
+
"access": "public"
|
|
90
|
+
}
|
|
91
|
+
}
|