@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.
Files changed (45) hide show
  1. package/CapawesomeCapacitorPasskeys.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +472 -0
  5. package/android/build.gradle +61 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/Passkeys.java +173 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/PasskeysPlugin.java +115 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/CustomException.java +20 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/CustomExceptions.java +12 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/options/CreatePasskeyOptions.java +38 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/options/GetPasskeyOptions.java +33 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/CreatePasskeyResult.java +22 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/GetPasskeyResult.java +22 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/classes/results/IsAvailableResult.java +22 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/Callback.java +5 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/NonEmptyResultCallback.java +7 -0
  18. package/android/src/main/java/io/capawesome/capacitorjs/plugins/passkeys/interfaces/Result.java +7 -0
  19. package/android/src/main/res/.gitkeep +0 -0
  20. package/dist/docs.json +1091 -0
  21. package/dist/esm/definitions.d.ts +567 -0
  22. package/dist/esm/definitions.js +47 -0
  23. package/dist/esm/definitions.js.map +1 -0
  24. package/dist/esm/index.d.ts +4 -0
  25. package/dist/esm/index.js +7 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/web.d.ts +18 -0
  28. package/dist/esm/web.js +184 -0
  29. package/dist/esm/web.js.map +1 -0
  30. package/dist/plugin.cjs.js +244 -0
  31. package/dist/plugin.cjs.js.map +1 -0
  32. package/dist/plugin.js +247 -0
  33. package/dist/plugin.js.map +1 -0
  34. package/ios/Plugin/Classes/Options/CreatePasskeyOptions.swift +81 -0
  35. package/ios/Plugin/Classes/Options/GetPasskeyOptions.swift +54 -0
  36. package/ios/Plugin/Classes/Results/CreatePasskeyResult.swift +28 -0
  37. package/ios/Plugin/Classes/Results/GetPasskeyResult.swift +34 -0
  38. package/ios/Plugin/Classes/Results/IsAvailableResult.swift +16 -0
  39. package/ios/Plugin/Enums/CustomError.swift +65 -0
  40. package/ios/Plugin/Info.plist +24 -0
  41. package/ios/Plugin/Passkeys.swift +118 -0
  42. package/ios/Plugin/PasskeysHelper.swift +20 -0
  43. package/ios/Plugin/PasskeysPlugin.swift +86 -0
  44. package/ios/Plugin/Protocols/Result.swift +5 -0
  45. 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
+ }
@@ -0,0 +1,5 @@
1
+ import Capacitor
2
+
3
+ @objc public protocol Result {
4
+ @objc func toJSObject() -> AnyObject
5
+ }
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
+ }