@capgo/capacitor-passkey 8.2.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/CapgoCapacitorPasskey.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +692 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/passkey/CapacitorPasskey.java +21 -0
- package/android/src/main/java/app/capgo/passkey/CapacitorPasskeyPlugin.java +299 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1034 -0
- package/dist/esm/definitions.d.ts +531 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +87 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal.d.ts +14 -0
- package/dist/esm/internal.js +2 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/shim.d.ts +8 -0
- package/dist/esm/shim.js +108 -0
- package/dist/esm/shim.js.map +1 -0
- package/dist/esm/web.d.ts +12 -0
- package/dist/esm/web.js +69 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/esm/webauthn.d.ts +23 -0
- package/dist/esm/webauthn.js +440 -0
- package/dist/esm/webauthn.js.map +1 -0
- package/dist/plugin.cjs.js +707 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +710 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapacitorPasskeyPlugin/CapacitorPasskey.swift +39 -0
- package/ios/Sources/CapacitorPasskeyPlugin/CapacitorPasskeyPlugin.swift +388 -0
- package/ios/Tests/CapacitorPasskeyPluginTests/CapacitorPasskeyPluginTests.swift +17 -0
- package/package.json +105 -0
- package/scripts/configure-native.js +263 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace = "app.capgo.passkey"
|
|
22
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError = false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
53
|
+
implementation project(':capacitor-android')
|
|
54
|
+
annotationProcessor project(':capacitor-android')
|
|
55
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
56
|
+
implementation "androidx.credentials:credentials:1.5.0"
|
|
57
|
+
implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
|
|
58
|
+
testImplementation "junit:junit:$junitVersion"
|
|
59
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
60
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package app.capgo.passkey;
|
|
2
|
+
|
|
3
|
+
import androidx.credentials.webauthn.PublicKeyCredentialCreationOptions;
|
|
4
|
+
import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions;
|
|
5
|
+
|
|
6
|
+
public class CapacitorPasskey {
|
|
7
|
+
|
|
8
|
+
public static final String PLUGIN_VERSION = "8.0.0";
|
|
9
|
+
|
|
10
|
+
public String getPluginVersion() {
|
|
11
|
+
return PLUGIN_VERSION;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public void validateCreationRequestJson(String requestJson) {
|
|
15
|
+
new PublicKeyCredentialCreationOptions(requestJson);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public void validateAssertionRequestJson(String requestJson) {
|
|
19
|
+
new PublicKeyCredentialRequestOptions(requestJson);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
package app.capgo.passkey;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.os.Build;
|
|
5
|
+
import androidx.core.content.ContextCompat;
|
|
6
|
+
import androidx.credentials.CreateCredentialResponse;
|
|
7
|
+
import androidx.credentials.CreatePublicKeyCredentialRequest;
|
|
8
|
+
import androidx.credentials.CreatePublicKeyCredentialResponse;
|
|
9
|
+
import androidx.credentials.Credential;
|
|
10
|
+
import androidx.credentials.CredentialManager;
|
|
11
|
+
import androidx.credentials.CredentialManagerCallback;
|
|
12
|
+
import androidx.credentials.GetCredentialRequest;
|
|
13
|
+
import androidx.credentials.GetCredentialResponse;
|
|
14
|
+
import androidx.credentials.GetPublicKeyCredentialOption;
|
|
15
|
+
import androidx.credentials.PublicKeyCredential;
|
|
16
|
+
import androidx.credentials.exceptions.CreateCredentialException;
|
|
17
|
+
import androidx.credentials.exceptions.GetCredentialException;
|
|
18
|
+
import androidx.credentials.exceptions.NoCredentialException;
|
|
19
|
+
import androidx.credentials.exceptions.domerrors.DomError;
|
|
20
|
+
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException;
|
|
21
|
+
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException;
|
|
22
|
+
import com.getcapacitor.JSObject;
|
|
23
|
+
import com.getcapacitor.Plugin;
|
|
24
|
+
import com.getcapacitor.PluginCall;
|
|
25
|
+
import com.getcapacitor.PluginConfig;
|
|
26
|
+
import com.getcapacitor.PluginMethod;
|
|
27
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
28
|
+
import java.net.URI;
|
|
29
|
+
import java.net.URISyntaxException;
|
|
30
|
+
import java.util.ArrayList;
|
|
31
|
+
import java.util.LinkedHashSet;
|
|
32
|
+
import java.util.List;
|
|
33
|
+
import org.json.JSONException;
|
|
34
|
+
import org.json.JSONObject;
|
|
35
|
+
|
|
36
|
+
@CapacitorPlugin(name = "CapacitorPasskey")
|
|
37
|
+
public class CapacitorPasskeyPlugin extends Plugin {
|
|
38
|
+
|
|
39
|
+
private final CapacitorPasskey implementation = new CapacitorPasskey();
|
|
40
|
+
private CredentialManager credentialManager;
|
|
41
|
+
|
|
42
|
+
@Override
|
|
43
|
+
public void load() {
|
|
44
|
+
super.load();
|
|
45
|
+
try {
|
|
46
|
+
credentialManager = CredentialManager.create(getContext());
|
|
47
|
+
} catch (Exception exception) {
|
|
48
|
+
credentialManager = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@PluginMethod
|
|
53
|
+
public void createCredential(final PluginCall call) {
|
|
54
|
+
if (!isCredentialManagerAvailable(call)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
final String requestJson = call.getString("requestJson");
|
|
59
|
+
if (requestJson == null || requestJson.isBlank()) {
|
|
60
|
+
rejectDom(call, "DataError", "requestJson is required.", null);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
implementation.validateCreationRequestJson(requestJson);
|
|
66
|
+
} catch (Exception exception) {
|
|
67
|
+
rejectDom(call, "SyntaxError", "Invalid passkey registration request JSON.", exception);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
final CreatePublicKeyCredentialRequest request;
|
|
72
|
+
try {
|
|
73
|
+
request = new CreatePublicKeyCredentialRequest(requestJson);
|
|
74
|
+
} catch (Exception exception) {
|
|
75
|
+
rejectDom(call, "SyntaxError", "Could not create the native registration request.", exception);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
bridge.executeOnMainThread(() -> {
|
|
80
|
+
Activity activity = getActivity();
|
|
81
|
+
if (activity == null) {
|
|
82
|
+
rejectDom(call, "NotAllowedError", "Activity not available.", null);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
credentialManager.createCredentialAsync(
|
|
87
|
+
activity,
|
|
88
|
+
request,
|
|
89
|
+
null,
|
|
90
|
+
ContextCompat.getMainExecutor(getContext()),
|
|
91
|
+
new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
|
|
92
|
+
@Override
|
|
93
|
+
public void onResult(CreateCredentialResponse response) {
|
|
94
|
+
if (!(response instanceof CreatePublicKeyCredentialResponse)) {
|
|
95
|
+
rejectDom(call, "UnknownError", "Unexpected native registration response.", null);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolveJson(call, ((CreatePublicKeyCredentialResponse) response).getRegistrationResponseJson());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Override
|
|
103
|
+
public void onError(CreateCredentialException exception) {
|
|
104
|
+
rejectCreateException(call, exception);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@PluginMethod
|
|
112
|
+
public void getCredential(final PluginCall call) {
|
|
113
|
+
if (!isCredentialManagerAvailable(call)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
final String requestJson = call.getString("requestJson");
|
|
118
|
+
if (requestJson == null || requestJson.isBlank()) {
|
|
119
|
+
rejectDom(call, "DataError", "requestJson is required.", null);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
implementation.validateAssertionRequestJson(requestJson);
|
|
125
|
+
} catch (Exception exception) {
|
|
126
|
+
rejectDom(call, "SyntaxError", "Invalid passkey authentication request JSON.", exception);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
final GetCredentialRequest request;
|
|
131
|
+
try {
|
|
132
|
+
request = new GetCredentialRequest(List.of(new GetPublicKeyCredentialOption(requestJson)));
|
|
133
|
+
} catch (Exception exception) {
|
|
134
|
+
rejectDom(call, "SyntaxError", "Could not create the native authentication request.", exception);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
bridge.executeOnMainThread(() -> {
|
|
139
|
+
Activity activity = getActivity();
|
|
140
|
+
if (activity == null) {
|
|
141
|
+
rejectDom(call, "NotAllowedError", "Activity not available.", null);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
credentialManager.getCredentialAsync(
|
|
146
|
+
activity,
|
|
147
|
+
request,
|
|
148
|
+
null,
|
|
149
|
+
ContextCompat.getMainExecutor(getContext()),
|
|
150
|
+
new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
|
|
151
|
+
@Override
|
|
152
|
+
public void onResult(GetCredentialResponse response) {
|
|
153
|
+
Credential credential = response.getCredential();
|
|
154
|
+
if (!(credential instanceof PublicKeyCredential)) {
|
|
155
|
+
rejectDom(call, "UnknownError", "Unexpected native authentication response.", null);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
resolveJson(call, ((PublicKeyCredential) credential).getAuthenticationResponseJson());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Override
|
|
163
|
+
public void onError(GetCredentialException exception) {
|
|
164
|
+
rejectGetException(call, exception);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@PluginMethod
|
|
172
|
+
public void getConfiguration(final PluginCall call) {
|
|
173
|
+
PluginConfig config = getConfig();
|
|
174
|
+
JSObject result = new JSObject();
|
|
175
|
+
String origin = normalizeOrigin(config.getString("origin"));
|
|
176
|
+
result.put("autoShim", config.getBoolean("autoShim", true));
|
|
177
|
+
result.put("domains", normalizeDomains(config.getArray("domains"), origin));
|
|
178
|
+
result.put("origin", origin);
|
|
179
|
+
result.put("platform", "android");
|
|
180
|
+
call.resolve(result);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@PluginMethod
|
|
184
|
+
public void isSupported(final PluginCall call) {
|
|
185
|
+
JSObject result = new JSObject();
|
|
186
|
+
result.put("available", credentialManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
|
|
187
|
+
result.put("conditionalMediation", false);
|
|
188
|
+
result.put("platform", "android");
|
|
189
|
+
call.resolve(result);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@PluginMethod
|
|
193
|
+
public void getPluginVersion(final PluginCall call) {
|
|
194
|
+
JSObject result = new JSObject();
|
|
195
|
+
result.put("version", implementation.getPluginVersion());
|
|
196
|
+
call.resolve(result);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private boolean isCredentialManagerAvailable(PluginCall call) {
|
|
200
|
+
if (credentialManager == null) {
|
|
201
|
+
rejectDom(call, "NotSupportedError", "Credential Manager is not available on this device.", null);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
|
206
|
+
rejectDom(call, "NotSupportedError", "Passkeys require Android API level 28 or higher.", null);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private void resolveJson(PluginCall call, String json) {
|
|
214
|
+
try {
|
|
215
|
+
call.resolve(JSObject.fromJSONObject(new JSONObject(json)));
|
|
216
|
+
} catch (JSONException exception) {
|
|
217
|
+
rejectDom(call, "UnknownError", "Native passkey response was not valid JSON.", exception);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private void rejectCreateException(PluginCall call, CreateCredentialException exception) {
|
|
222
|
+
if (exception instanceof CreatePublicKeyCredentialDomException) {
|
|
223
|
+
DomError domError = ((CreatePublicKeyCredentialDomException) exception).getDomError();
|
|
224
|
+
rejectDom(call, domError.getClass().getSimpleName(), messageFor(exception, "Passkey registration failed."), exception);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
rejectDom(call, "UnknownError", messageFor(exception, "Passkey registration failed."), exception);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private void rejectGetException(PluginCall call, GetCredentialException exception) {
|
|
232
|
+
if (exception instanceof GetPublicKeyCredentialDomException) {
|
|
233
|
+
DomError domError = ((GetPublicKeyCredentialDomException) exception).getDomError();
|
|
234
|
+
rejectDom(call, domError.getClass().getSimpleName(), messageFor(exception, "Passkey authentication failed."), exception);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (exception instanceof NoCredentialException) {
|
|
239
|
+
rejectDom(call, "NotFoundError", "No matching passkey was found.", exception);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
rejectDom(call, "UnknownError", messageFor(exception, "Passkey authentication failed."), exception);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private String messageFor(CreateCredentialException exception, String fallback) {
|
|
247
|
+
CharSequence message = exception.getErrorMessage();
|
|
248
|
+
return message != null ? message.toString() : fallback;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private String messageFor(GetCredentialException exception, String fallback) {
|
|
252
|
+
CharSequence message = exception.getErrorMessage();
|
|
253
|
+
return message != null ? message.toString() : fallback;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private void rejectDom(PluginCall call, String name, String message, Exception exception) {
|
|
257
|
+
JSObject data = new JSObject();
|
|
258
|
+
data.put("name", name);
|
|
259
|
+
call.reject(message, name, exception, data);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private ArrayList<String> normalizeDomains(String[] configuredDomains, String origin) {
|
|
263
|
+
LinkedHashSet<String> domains = new LinkedHashSet<>();
|
|
264
|
+
if (configuredDomains != null) {
|
|
265
|
+
for (String value : configuredDomains) {
|
|
266
|
+
String normalized = normalizeDomain(value);
|
|
267
|
+
if (normalized != null) {
|
|
268
|
+
domains.add(normalized);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
String originDomain = normalizeDomain(origin);
|
|
274
|
+
if (originDomain != null) {
|
|
275
|
+
domains.add(originDomain);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return new ArrayList<>(domains);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private String normalizeOrigin(String value) {
|
|
282
|
+
return value == null || value.isBlank() ? null : value;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private String normalizeDomain(String value) {
|
|
286
|
+
if (value == null || value.isBlank()) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
URI uri = new URI(value);
|
|
292
|
+
if (uri.getHost() != null && !uri.getHost().isBlank()) {
|
|
293
|
+
return uri.getHost();
|
|
294
|
+
}
|
|
295
|
+
} catch (URISyntaxException ignored) {}
|
|
296
|
+
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
File without changes
|