@dexterai/connect 0.14.0 → 0.15.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/dist/{chunk-IUZBHWTP.js → chunk-RLVMYLHC.js} +81 -148
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +4 -1
|
@@ -124,57 +124,8 @@ function onStorageEvent(e) {
|
|
|
124
124
|
}
|
|
125
125
|
var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
|
|
126
126
|
|
|
127
|
-
// src/
|
|
128
|
-
|
|
129
|
-
const bin = atob(s);
|
|
130
|
-
const out = new Uint8Array(bin.length);
|
|
131
|
-
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
function base64urlToBytes(s) {
|
|
135
|
-
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
136
|
-
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
137
|
-
return base64ToBytes(b64);
|
|
138
|
-
}
|
|
139
|
-
function bytesToBase64(bytes) {
|
|
140
|
-
let bin = "";
|
|
141
|
-
for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
|
|
142
|
-
return btoa(bin);
|
|
143
|
-
}
|
|
144
|
-
function bytesToBase64url(bytes) {
|
|
145
|
-
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
146
|
-
}
|
|
147
|
-
function compactSignatureToDer(compact) {
|
|
148
|
-
if (compact.length !== 64) {
|
|
149
|
-
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
150
|
-
}
|
|
151
|
-
const r = derInteger(compact.subarray(0, 32));
|
|
152
|
-
const s = derInteger(compact.subarray(32, 64));
|
|
153
|
-
const body = new Uint8Array(r.length + s.length);
|
|
154
|
-
body.set(r, 0);
|
|
155
|
-
body.set(s, r.length);
|
|
156
|
-
const out = new Uint8Array(2 + body.length);
|
|
157
|
-
out[0] = 48;
|
|
158
|
-
out[1] = body.length;
|
|
159
|
-
out.set(body, 2);
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
|
-
function derInteger(component) {
|
|
163
|
-
let start = 0;
|
|
164
|
-
while (start < component.length - 1 && component[start] === 0) start += 1;
|
|
165
|
-
let content = component.subarray(start);
|
|
166
|
-
if (content[0] & 128) {
|
|
167
|
-
const padded = new Uint8Array(content.length + 1);
|
|
168
|
-
padded[0] = 0;
|
|
169
|
-
padded.set(content, 1);
|
|
170
|
-
content = padded;
|
|
171
|
-
}
|
|
172
|
-
const out = new Uint8Array(2 + content.length);
|
|
173
|
-
out[0] = 2;
|
|
174
|
-
out[1] = content.length;
|
|
175
|
-
out.set(content, 2);
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
127
|
+
// src/enroll.ts
|
|
128
|
+
import { startRegistration } from "@simplewebauthn/browser";
|
|
178
129
|
|
|
179
130
|
// src/popup.ts
|
|
180
131
|
var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
|
|
@@ -282,9 +233,19 @@ async function createWallet(config = {}) {
|
|
|
282
233
|
config.onPhase?.("challenge");
|
|
283
234
|
const options = await fetchEnrollChallenge(apiBase);
|
|
284
235
|
config.onPhase?.("passkey");
|
|
285
|
-
const
|
|
236
|
+
const optionsJSON = {
|
|
237
|
+
...options,
|
|
238
|
+
rp: { ...options.rp, id: options.rp.id ?? rpId, name: "Dexter" },
|
|
239
|
+
user: { ...options.user, name, displayName: name }
|
|
240
|
+
};
|
|
241
|
+
let regResponse;
|
|
242
|
+
try {
|
|
243
|
+
regResponse = await startRegistration({ optionsJSON });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
246
|
+
}
|
|
286
247
|
config.onPhase?.("verifying");
|
|
287
|
-
const enrolled = await submitEnrollComplete(apiBase,
|
|
248
|
+
const enrolled = await submitEnrollComplete(apiBase, regResponse);
|
|
288
249
|
config.onPhase?.("finalizing");
|
|
289
250
|
const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
|
|
290
251
|
setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
|
|
@@ -318,38 +279,11 @@ async function fetchEnrollChallenge(apiBase) {
|
|
|
318
279
|
}
|
|
319
280
|
return data.options;
|
|
320
281
|
}
|
|
321
|
-
async function
|
|
322
|
-
let credential;
|
|
323
|
-
try {
|
|
324
|
-
credential = await navigator.credentials.create({
|
|
325
|
-
publicKey: buildCreationOptions(options, name, rpId)
|
|
326
|
-
});
|
|
327
|
-
} catch (err) {
|
|
328
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
329
|
-
}
|
|
330
|
-
if (!credential || credential.type !== "public-key") {
|
|
331
|
-
throw new ConnectError("no_credential", "authenticator returned no credential");
|
|
332
|
-
}
|
|
333
|
-
return credential;
|
|
334
|
-
}
|
|
335
|
-
async function submitEnrollComplete(apiBase, credential) {
|
|
336
|
-
const attestation = credential.response;
|
|
337
|
-
const credentialJson = {
|
|
338
|
-
id: credential.id,
|
|
339
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
340
|
-
type: credential.type,
|
|
341
|
-
response: {
|
|
342
|
-
attestationObject: bytesToBase64url(new Uint8Array(attestation.attestationObject)),
|
|
343
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(attestation.clientDataJSON)),
|
|
344
|
-
transports: typeof attestation.getTransports === "function" ? attestation.getTransports() : []
|
|
345
|
-
},
|
|
346
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {},
|
|
347
|
-
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
348
|
-
};
|
|
282
|
+
async function submitEnrollComplete(apiBase, response) {
|
|
349
283
|
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
|
|
350
284
|
method: "POST",
|
|
351
285
|
headers: { "content-type": "application/json" },
|
|
352
|
-
body: JSON.stringify({ credential:
|
|
286
|
+
body: JSON.stringify({ credential: response })
|
|
353
287
|
});
|
|
354
288
|
if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
|
|
355
289
|
return await res.json();
|
|
@@ -363,32 +297,6 @@ async function initializeVault(apiBase, userHandle, credentialId) {
|
|
|
363
297
|
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
364
298
|
return await res.json();
|
|
365
299
|
}
|
|
366
|
-
function toBuf(b64url) {
|
|
367
|
-
return base64urlToBytes(b64url).buffer.slice(0);
|
|
368
|
-
}
|
|
369
|
-
function buildCreationOptions(o, name, rpId) {
|
|
370
|
-
return {
|
|
371
|
-
// rp.name = the site shown in the keychain; user.name/displayName = the
|
|
372
|
-
// wallet label the user sees. We override the server's user.name (a raw,
|
|
373
|
-
// unreadable handle) with the chosen wallet name.
|
|
374
|
-
rp: { id: o.rp.id ?? rpId, name: "Dexter" },
|
|
375
|
-
user: {
|
|
376
|
-
id: toBuf(o.user.id),
|
|
377
|
-
name,
|
|
378
|
-
displayName: name
|
|
379
|
-
},
|
|
380
|
-
challenge: toBuf(o.challenge),
|
|
381
|
-
pubKeyCredParams: o.pubKeyCredParams,
|
|
382
|
-
timeout: o.timeout,
|
|
383
|
-
excludeCredentials: o.excludeCredentials?.map((c) => ({
|
|
384
|
-
id: toBuf(c.id),
|
|
385
|
-
type: c.type,
|
|
386
|
-
transports: c.transports
|
|
387
|
-
})),
|
|
388
|
-
authenticatorSelection: o.authenticatorSelection,
|
|
389
|
-
attestation: o.attestation
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
300
|
async function readErrorCode(res) {
|
|
393
301
|
try {
|
|
394
302
|
const body = await res.json();
|
|
@@ -399,6 +307,7 @@ async function readErrorCode(res) {
|
|
|
399
307
|
}
|
|
400
308
|
|
|
401
309
|
// src/relay.ts
|
|
310
|
+
import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
|
|
402
311
|
var DEFAULT_API_BASE2 = "https://api.dexter.cash";
|
|
403
312
|
var ANON_SIGN_BASE = "/api/passkey-anon/sign";
|
|
404
313
|
async function passkeyLogin(config = {}, onPhase) {
|
|
@@ -408,13 +317,21 @@ async function passkeyLogin(config = {}, onPhase) {
|
|
|
408
317
|
apiBase: config.apiBase
|
|
409
318
|
});
|
|
410
319
|
}
|
|
320
|
+
if (!browserSupportsWebAuthn()) {
|
|
321
|
+
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
322
|
+
}
|
|
411
323
|
const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
|
|
412
324
|
onPhase?.("challenge");
|
|
413
325
|
const options = await fetchLoginChallenge(apiBase);
|
|
414
326
|
onPhase?.("passkey");
|
|
415
|
-
|
|
327
|
+
let response;
|
|
328
|
+
try {
|
|
329
|
+
response = await startAuthentication({ optionsJSON: options });
|
|
330
|
+
} catch (err) {
|
|
331
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
332
|
+
}
|
|
416
333
|
onPhase?.("verifying");
|
|
417
|
-
return submitLogin(apiBase,
|
|
334
|
+
return submitLogin(apiBase, response);
|
|
418
335
|
}
|
|
419
336
|
async function continueWithDexter(config = {}, onPhase) {
|
|
420
337
|
if (shouldUsePopup(config.transport)) {
|
|
@@ -446,47 +363,11 @@ async function fetchLoginChallenge(apiBase) {
|
|
|
446
363
|
}
|
|
447
364
|
return data.options;
|
|
448
365
|
}
|
|
449
|
-
async function
|
|
450
|
-
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
451
|
-
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
452
|
-
}
|
|
453
|
-
let credential;
|
|
454
|
-
try {
|
|
455
|
-
credential = await navigator.credentials.get({
|
|
456
|
-
publicKey: {
|
|
457
|
-
challenge: base64urlToBytes(options.challenge).buffer.slice(0),
|
|
458
|
-
rpId: options.rpId,
|
|
459
|
-
timeout: options.timeout ?? 6e4,
|
|
460
|
-
userVerification: options.userVerification ?? "required"
|
|
461
|
-
// No allowCredentials — discoverable resident-key login.
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
} catch (err) {
|
|
465
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
466
|
-
}
|
|
467
|
-
if (!credential || credential.type !== "public-key") {
|
|
468
|
-
throw new ConnectError("no_credential", "WebAuthn returned no credential");
|
|
469
|
-
}
|
|
470
|
-
return credential;
|
|
471
|
-
}
|
|
472
|
-
async function submitLogin(apiBase, credential) {
|
|
473
|
-
const assertion = credential.response;
|
|
474
|
-
const credentialJson = {
|
|
475
|
-
id: credential.id,
|
|
476
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
477
|
-
type: credential.type,
|
|
478
|
-
response: {
|
|
479
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(assertion.clientDataJSON)),
|
|
480
|
-
authenticatorData: bytesToBase64url(new Uint8Array(assertion.authenticatorData)),
|
|
481
|
-
signature: bytesToBase64url(new Uint8Array(assertion.signature)),
|
|
482
|
-
userHandle: assertion.userHandle ? bytesToBase64url(new Uint8Array(assertion.userHandle)) : null
|
|
483
|
-
},
|
|
484
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {}
|
|
485
|
-
};
|
|
366
|
+
async function submitLogin(apiBase, response) {
|
|
486
367
|
const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/passkey-login`, {
|
|
487
368
|
method: "POST",
|
|
488
369
|
headers: { "content-type": "application/json" },
|
|
489
|
-
body: JSON.stringify({ credential:
|
|
370
|
+
body: JSON.stringify({ credential: response })
|
|
490
371
|
});
|
|
491
372
|
if (!res.ok) {
|
|
492
373
|
throw new ConnectError(await readErrorCode2(res), `passkey-login ${res.status}`);
|
|
@@ -510,6 +391,58 @@ async function readErrorCode2(res) {
|
|
|
510
391
|
return `http_${res.status}`;
|
|
511
392
|
}
|
|
512
393
|
|
|
394
|
+
// src/base64.ts
|
|
395
|
+
function base64ToBytes(s) {
|
|
396
|
+
const bin = atob(s);
|
|
397
|
+
const out = new Uint8Array(bin.length);
|
|
398
|
+
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function base64urlToBytes(s) {
|
|
402
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
403
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
404
|
+
return base64ToBytes(b64);
|
|
405
|
+
}
|
|
406
|
+
function bytesToBase64(bytes) {
|
|
407
|
+
let bin = "";
|
|
408
|
+
for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
|
|
409
|
+
return btoa(bin);
|
|
410
|
+
}
|
|
411
|
+
function bytesToBase64url(bytes) {
|
|
412
|
+
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
413
|
+
}
|
|
414
|
+
function compactSignatureToDer(compact) {
|
|
415
|
+
if (compact.length !== 64) {
|
|
416
|
+
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
417
|
+
}
|
|
418
|
+
const r = derInteger(compact.subarray(0, 32));
|
|
419
|
+
const s = derInteger(compact.subarray(32, 64));
|
|
420
|
+
const body = new Uint8Array(r.length + s.length);
|
|
421
|
+
body.set(r, 0);
|
|
422
|
+
body.set(s, r.length);
|
|
423
|
+
const out = new Uint8Array(2 + body.length);
|
|
424
|
+
out[0] = 48;
|
|
425
|
+
out[1] = body.length;
|
|
426
|
+
out.set(body, 2);
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function derInteger(component) {
|
|
430
|
+
let start = 0;
|
|
431
|
+
while (start < component.length - 1 && component[start] === 0) start += 1;
|
|
432
|
+
let content = component.subarray(start);
|
|
433
|
+
if (content[0] & 128) {
|
|
434
|
+
const padded = new Uint8Array(content.length + 1);
|
|
435
|
+
padded[0] = 0;
|
|
436
|
+
padded.set(content, 1);
|
|
437
|
+
content = padded;
|
|
438
|
+
}
|
|
439
|
+
const out = new Uint8Array(2 + content.length);
|
|
440
|
+
out[0] = 2;
|
|
441
|
+
out[1] = content.length;
|
|
442
|
+
out.set(content, 2);
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
|
|
513
446
|
// src/anon-policy.ts
|
|
514
447
|
var DEFAULT_API_BASE3 = "https://api.dexter.cash";
|
|
515
448
|
var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
|
package/dist/index.js
CHANGED
package/dist/react.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,5 +32,8 @@
|
|
|
32
32
|
"tsup": "^8.5.0",
|
|
33
33
|
"typescript": "^5.6.2",
|
|
34
34
|
"vitest": "^4.1.9"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@simplewebauthn/browser": "^13.3.0"
|
|
35
38
|
}
|
|
36
39
|
}
|