@accesly/react 1.0.0-pre.0 → 1.0.0-pre.2
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/CHANGELOG.md +72 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.cjs +88 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -2
- package/dist/index.d.ts +97 -2
- package/dist/index.js +89 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Environment, CognitoConfig, AuthClient, SessionStorage, DeviceStore, TelemetrySink, TokenManager, AccesslyEndpoints, AuthStatus, CredentialRecord } from '@accesly/core';
|
|
1
|
+
import { Environment, CognitoConfig, AuthClient, SessionStorage, DeviceStore, TelemetrySink, TokenManager, AccesslyEndpoints, AuthStatus, CredentialRecord, EncryptedEnvelope } from '@accesly/core';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
@@ -127,6 +127,22 @@ interface CreateWalletInput {
|
|
|
127
127
|
readonly credentialId?: Uint8Array;
|
|
128
128
|
/** Optional. See `credentialId`. */
|
|
129
129
|
readonly prfSalt?: Uint8Array;
|
|
130
|
+
/**
|
|
131
|
+
* Password de Cognito en plano (`Uint8Array` codificado UTF-8).
|
|
132
|
+
*
|
|
133
|
+
* Recovery v2 (Fase 1, 2026-06-15): si se provee, el SDK deriva
|
|
134
|
+
* `recoveryKey = PBKDF2(password, recoverySalt, 600k)` y la usa para
|
|
135
|
+
* cifrar F3 antes de enviarlo al backend, en vez de usar
|
|
136
|
+
* `encryptionKeys[2]`. El backend almacena F3 cifrado con esa key —
|
|
137
|
+
* SOLO descifrable client-side con el mismo password.
|
|
138
|
+
*
|
|
139
|
+
* Sin esta prop el wallet se crea pero NO podrá recuperarse vía OTP
|
|
140
|
+
* (F3 quedará cifrado con `encryptionKeys[2]`, igual que en 0.x).
|
|
141
|
+
*
|
|
142
|
+
* El caller es responsable de zeroizar este buffer tras `createWallet`
|
|
143
|
+
* (el SDK no lo retiene en memoria).
|
|
144
|
+
*/
|
|
145
|
+
readonly cognitoPassword?: Uint8Array;
|
|
130
146
|
}
|
|
131
147
|
interface CreatedWalletInfo {
|
|
132
148
|
readonly walletAddress: string;
|
|
@@ -409,11 +425,90 @@ interface YieldNamespace {
|
|
|
409
425
|
declare class NotImplementedYetError extends Error {
|
|
410
426
|
constructor(namespace: string, method: string);
|
|
411
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Recovery v2 — OTP por email + password de Cognito (Fase 1, 2026-06-15).
|
|
430
|
+
*
|
|
431
|
+
* Flujo desde la UI:
|
|
432
|
+
* 1. `recovery.requestOtp({ email })` → manda el OTP por SES.
|
|
433
|
+
* 2. `recovery.verifyOtp({ email, code })` → devuelve `recoveryJwt`.
|
|
434
|
+
* 3. `recovery.finalize({ email, password, recoveryJwt })` orquesta todo:
|
|
435
|
+
* - GET /fragments/3 con el JWT
|
|
436
|
+
* - Deriva recoveryKey con el password + recoverySalt del backend
|
|
437
|
+
* - Decifra F3
|
|
438
|
+
* - Decifra F2 (vía session key ECDH)
|
|
439
|
+
* - Combina F2+F3 → seed ed25519 reconstruida
|
|
440
|
+
* - Genera new passkey + new Shamir split (F1', F2', F3')
|
|
441
|
+
* - Re-cifra F3' con la misma recoveryKey + nuevo salt
|
|
442
|
+
* - Firma la tx `rotate_signer` localmente
|
|
443
|
+
* - POST /recovery/finalize con todo
|
|
444
|
+
* - Persiste new CredentialRecord local
|
|
445
|
+
* - Zero-iza la seed
|
|
446
|
+
*/
|
|
447
|
+
interface ReconstructedSeed {
|
|
448
|
+
/** 32-byte ed25519 seed reconstruida vía Shamir(F2_recovery + F3). CALLER ZEROIZE. */
|
|
449
|
+
readonly privateSeed: Uint8Array;
|
|
450
|
+
/** 32-byte ed25519 public key derivada. */
|
|
451
|
+
readonly publicKey: Uint8Array;
|
|
452
|
+
/** 32-byte recoveryKey derivada del password — útil para re-cifrar F2'/F3' nuevos. */
|
|
453
|
+
readonly recoveryKey: Uint8Array;
|
|
454
|
+
/** Base64 32-byte salt que vino del backend. */
|
|
455
|
+
readonly recoverySalt: string;
|
|
456
|
+
}
|
|
457
|
+
interface RecoveryNamespace {
|
|
458
|
+
/** Pide OTP. Backend rate-limita; el caller debe respetar `cooldownSeconds`. */
|
|
459
|
+
requestOtp(input: {
|
|
460
|
+
email: string;
|
|
461
|
+
}): Promise<{
|
|
462
|
+
cooldownSeconds: number;
|
|
463
|
+
expiresInSeconds: number;
|
|
464
|
+
}>;
|
|
465
|
+
/** Verifica OTP. Devuelve `recoveryJwt` con TTL 5min. */
|
|
466
|
+
verifyOtp(input: {
|
|
467
|
+
email: string;
|
|
468
|
+
code: string;
|
|
469
|
+
}): Promise<{
|
|
470
|
+
recoveryJwt: string;
|
|
471
|
+
expiresAt: number;
|
|
472
|
+
}>;
|
|
473
|
+
/**
|
|
474
|
+
* Descarga `/fragments/3`, descifra F2_recovery + F3 con la `recoveryKey`
|
|
475
|
+
* derivada del password y reconstruye la seed via Shamir.
|
|
476
|
+
*
|
|
477
|
+
* El caller DEBE zero-izar `result.privateSeed` y `result.recoveryKey`
|
|
478
|
+
* tras firmar la rotación + cifrar las nuevas F1'/F2'/F3'.
|
|
479
|
+
*
|
|
480
|
+
* El caller también es responsable de zeroizar `cognitoPassword` después.
|
|
481
|
+
*/
|
|
482
|
+
reconstructSeed(input: {
|
|
483
|
+
cognitoPassword: Uint8Array;
|
|
484
|
+
recoveryJwt: string;
|
|
485
|
+
}): Promise<ReconstructedSeed>;
|
|
486
|
+
/**
|
|
487
|
+
* Envía la rotación al backend tras que el caller haya construido la tx
|
|
488
|
+
* `rotate_signer` y firmado el auth entry localmente.
|
|
489
|
+
*/
|
|
490
|
+
submitFinalize(input: {
|
|
491
|
+
recoveryJwt: string;
|
|
492
|
+
unsignedXdr: string;
|
|
493
|
+
newSecp256r1Pubkey: string;
|
|
494
|
+
newFragmentF1Encrypted: EncryptedEnvelope;
|
|
495
|
+
newFragmentF2Encrypted: EncryptedEnvelope;
|
|
496
|
+
newFragmentF2Recovery: EncryptedEnvelope;
|
|
497
|
+
newFragmentF3Encrypted: EncryptedEnvelope;
|
|
498
|
+
newRecoverySalt: string;
|
|
499
|
+
newEmailCommitment: string;
|
|
500
|
+
}): Promise<{
|
|
501
|
+
walletAddress: string;
|
|
502
|
+
txHash: string;
|
|
503
|
+
status: string;
|
|
504
|
+
}>;
|
|
505
|
+
}
|
|
412
506
|
interface AcceslyHook {
|
|
413
507
|
readonly auth: AuthNamespace;
|
|
414
508
|
readonly wallet: WalletNamespace;
|
|
415
509
|
readonly tx: TxNamespace;
|
|
416
510
|
readonly kyc: KycNamespace;
|
|
511
|
+
readonly recovery: RecoveryNamespace;
|
|
417
512
|
readonly session: SessionNamespace;
|
|
418
513
|
readonly settings: SettingsNamespace;
|
|
419
514
|
readonly yieldOps: YieldNamespace;
|
|
@@ -446,4 +541,4 @@ declare function useAccesly(): AcceslyHook;
|
|
|
446
541
|
*/
|
|
447
542
|
declare const REACT_ADAPTER_VERSION = "0.0.0";
|
|
448
543
|
|
|
449
|
-
export { AcceslyContext, type AcceslyContextValue, type AcceslyHook, AcceslyProvider, type AcceslyProviderProps, type AuthNamespace, type CreateWalletInput, type CreatedWalletInfo, ENVIRONMENT_DEFAULTS, type EnsureWalletResult, type EnvironmentDefaults, type KycNamespace, NotImplementedYetError, REACT_ADAPTER_VERSION, type RemoteWalletInfo, type RetryDeployResult, type SendXlmInput, type SendXlmResult, type SessionNamespace, type SettingsNamespace, type TxNamespace, type WalletNamespace, type WalletStatus, type YieldNamespace, useAccesly };
|
|
544
|
+
export { AcceslyContext, type AcceslyContextValue, type AcceslyHook, AcceslyProvider, type AcceslyProviderProps, type AuthNamespace, type CreateWalletInput, type CreatedWalletInfo, ENVIRONMENT_DEFAULTS, type EnsureWalletResult, type EnvironmentDefaults, type KycNamespace, NotImplementedYetError, REACT_ADAPTER_VERSION, type RecoveryNamespace, type RemoteWalletInfo, type RetryDeployResult, type SendXlmInput, type SendXlmResult, type SessionNamespace, type SettingsNamespace, type TxNamespace, type WalletNamespace, type WalletStatus, type YieldNamespace, useAccesly };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CognitoAuthClient, InMemorySessionStorage, InMemoryDeviceStore, TokenManager, AccesslyApiClient, AccesslyEndpoints, normalizeSecp256r1Pubkey, createWallet, computeSmartAccountAddress, signTransaction, generateX25519Keypair, unwrapSessionFragment2, reconstructFromPlainAndEncrypted, signSorobanAuthEntry, AccesslyApiError } from '@accesly/core';
|
|
1
|
+
import { CognitoAuthClient, InMemorySessionStorage, InMemoryDeviceStore, TokenManager, AccesslyApiClient, AccesslyEndpoints, normalizeSecp256r1Pubkey, createWallet, generateRecoverySalt, decryptAesGcm, deriveRecoveryKey, encryptAesGcm, emailHashBytes, computeSmartAccountAddress, signTransaction, generateX25519Keypair, unwrapSessionFragment2, reconstructFromPlainAndEncrypted, signSorobanAuthEntry, reconstructKey, AccesslyApiError } from '@accesly/core';
|
|
2
2
|
import { createContext, useMemo, useState, useRef, useEffect, useContext } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -189,7 +189,10 @@ function useAccesly() {
|
|
|
189
189
|
emailCommitment: hexFromBytes(params.emailCommitment),
|
|
190
190
|
secp256r1Pubkey: hexFromBytes(params.secp256r1Pubkey),
|
|
191
191
|
fragmentF2: encodeFragmentToWire(params.fragmentF2),
|
|
192
|
-
fragmentF3: encodeFragmentToWire(params.fragmentF3)
|
|
192
|
+
fragmentF3: encodeFragmentToWire(params.fragmentF3),
|
|
193
|
+
...params.fragmentF2Recovery ? { fragmentF2Recovery: encodeFragmentToWire(params.fragmentF2Recovery) } : {},
|
|
194
|
+
...params.emailHash ? { emailHash: params.emailHash } : {},
|
|
195
|
+
...params.recoverySalt ? { recoverySalt: params.recoverySalt } : {}
|
|
193
196
|
});
|
|
194
197
|
return res.walletAddress;
|
|
195
198
|
};
|
|
@@ -270,6 +273,28 @@ function useAccesly() {
|
|
|
270
273
|
emailSalt: input.emailSalt,
|
|
271
274
|
encryptionKeys: input.encryptionKeys
|
|
272
275
|
});
|
|
276
|
+
let fragmentF3ToSend = created.encryptedFragments[2];
|
|
277
|
+
let fragmentF2Recovery;
|
|
278
|
+
let recoverySaltBase64;
|
|
279
|
+
if (input.cognitoPassword) {
|
|
280
|
+
const recoverySalt = generateRecoverySalt();
|
|
281
|
+
const f2Plain = decryptAesGcm(created.encryptedFragments[1], input.encryptionKeys[1]);
|
|
282
|
+
const f3Plain = decryptAesGcm(created.encryptedFragments[2], input.encryptionKeys[2]);
|
|
283
|
+
const recoveryKey = deriveRecoveryKey({
|
|
284
|
+
password: input.cognitoPassword,
|
|
285
|
+
salt: recoverySalt
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
fragmentF2Recovery = encryptAesGcm(f2Plain, recoveryKey);
|
|
289
|
+
fragmentF3ToSend = encryptAesGcm(f3Plain, recoveryKey);
|
|
290
|
+
} finally {
|
|
291
|
+
for (let i = 0; i < recoveryKey.length; i += 1) recoveryKey[i] = 0;
|
|
292
|
+
for (let i = 0; i < f2Plain.length; i += 1) f2Plain[i] = 0;
|
|
293
|
+
for (let i = 0; i < f3Plain.length; i += 1) f3Plain[i] = 0;
|
|
294
|
+
}
|
|
295
|
+
recoverySaltBase64 = base64FromBytes(recoverySalt);
|
|
296
|
+
}
|
|
297
|
+
const emailHashHex = hexFromBytes(emailHashBytes(input.email));
|
|
273
298
|
const predictedAddress = await computeSmartAccountAddress({
|
|
274
299
|
ownerPubkey: created.publicKey,
|
|
275
300
|
deployerAddress: stellarConfig.deployerAddress,
|
|
@@ -283,7 +308,7 @@ function useAccesly() {
|
|
|
283
308
|
secp256r1Pubkey: secp256r1Canonical,
|
|
284
309
|
fragmentF1Encrypted: created.encryptedFragments[0],
|
|
285
310
|
fragmentF2Encrypted: created.encryptedFragments[1],
|
|
286
|
-
fragmentF3Encrypted:
|
|
311
|
+
fragmentF3Encrypted: fragmentF3ToSend,
|
|
287
312
|
publicKey: created.publicKey,
|
|
288
313
|
emailCommitment: created.emailCommitment,
|
|
289
314
|
prfSalt: input.prfSalt,
|
|
@@ -302,7 +327,10 @@ function useAccesly() {
|
|
|
302
327
|
emailCommitment: created.emailCommitment,
|
|
303
328
|
secp256r1Pubkey: secp256r1Canonical,
|
|
304
329
|
fragmentF2: created.encryptedFragments[1],
|
|
305
|
-
fragmentF3:
|
|
330
|
+
fragmentF3: fragmentF3ToSend,
|
|
331
|
+
emailHash: emailHashHex,
|
|
332
|
+
...fragmentF2Recovery ? { fragmentF2Recovery } : {},
|
|
333
|
+
...recoverySaltBase64 ? { recoverySalt: recoverySaltBase64 } : {}
|
|
306
334
|
});
|
|
307
335
|
} catch (err) {
|
|
308
336
|
if (!isSorobanDeployPendingError(err)) throw err;
|
|
@@ -503,6 +531,62 @@ function useAccesly() {
|
|
|
503
531
|
}),
|
|
504
532
|
[ctx]
|
|
505
533
|
);
|
|
534
|
+
const recovery = useMemo(
|
|
535
|
+
() => ({
|
|
536
|
+
async requestOtp(input) {
|
|
537
|
+
return ctx.endpoints.requestRecoveryOtp(input);
|
|
538
|
+
},
|
|
539
|
+
async verifyOtp(input) {
|
|
540
|
+
return ctx.endpoints.verifyRecoveryOtp(input);
|
|
541
|
+
},
|
|
542
|
+
async reconstructSeed(input) {
|
|
543
|
+
const frag = await ctx.endpoints.getFragment3(input.recoveryJwt);
|
|
544
|
+
if (!frag.fragmentF2Recovery) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
"recovery.reconstructSeed: la wallet fue creada antes de Fase 1 y no tiene F2 cipher-bound a recoveryKey. No es recuperable v\xEDa OTP."
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const recoverySalt = base64ToBytes(frag.recoverySalt);
|
|
550
|
+
const recoveryKey = deriveRecoveryKey({
|
|
551
|
+
password: input.cognitoPassword,
|
|
552
|
+
salt: recoverySalt
|
|
553
|
+
});
|
|
554
|
+
const f2Envelope = {
|
|
555
|
+
ciphertext: base64ToBytes(frag.fragmentF2Recovery.ciphertext),
|
|
556
|
+
nonce: base64ToBytes(frag.fragmentF2Recovery.nonce)
|
|
557
|
+
};
|
|
558
|
+
const f3Envelope = {
|
|
559
|
+
ciphertext: base64ToBytes(frag.fragmentF3Encrypted.ciphertext),
|
|
560
|
+
nonce: base64ToBytes(frag.fragmentF3Encrypted.nonce)
|
|
561
|
+
};
|
|
562
|
+
const seedResult = reconstructKey({
|
|
563
|
+
fragments: [
|
|
564
|
+
{ envelope: f2Envelope, key: recoveryKey },
|
|
565
|
+
{ envelope: f3Envelope, key: recoveryKey }
|
|
566
|
+
]
|
|
567
|
+
});
|
|
568
|
+
return {
|
|
569
|
+
privateSeed: seedResult.privateSeed,
|
|
570
|
+
publicKey: seedResult.publicKey,
|
|
571
|
+
recoveryKey,
|
|
572
|
+
recoverySalt: frag.recoverySalt
|
|
573
|
+
};
|
|
574
|
+
},
|
|
575
|
+
async submitFinalize(input) {
|
|
576
|
+
return ctx.endpoints.finalizeRecovery(input.recoveryJwt, {
|
|
577
|
+
unsignedXdr: input.unsignedXdr,
|
|
578
|
+
newSecp256r1Pubkey: input.newSecp256r1Pubkey,
|
|
579
|
+
newFragmentF1Encrypted: encodeFragmentToWire(input.newFragmentF1Encrypted),
|
|
580
|
+
newFragmentF2Encrypted: encodeFragmentToWire(input.newFragmentF2Encrypted),
|
|
581
|
+
newFragmentF2Recovery: encodeFragmentToWire(input.newFragmentF2Recovery),
|
|
582
|
+
newFragmentF3Encrypted: encodeFragmentToWire(input.newFragmentF3Encrypted),
|
|
583
|
+
newRecoverySalt: input.newRecoverySalt,
|
|
584
|
+
newEmailCommitment: input.newEmailCommitment
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}),
|
|
588
|
+
[ctx]
|
|
589
|
+
);
|
|
506
590
|
const session = useMemo(
|
|
507
591
|
() => ({
|
|
508
592
|
async create() {
|
|
@@ -545,7 +629,7 @@ function useAccesly() {
|
|
|
545
629
|
}),
|
|
546
630
|
[]
|
|
547
631
|
);
|
|
548
|
-
return { auth, wallet, tx, kyc, session, settings, yieldOps, _internal: ctx };
|
|
632
|
+
return { auth, wallet, tx, kyc, recovery, session, settings, yieldOps, _internal: ctx };
|
|
549
633
|
}
|
|
550
634
|
function coderHelpers() {
|
|
551
635
|
function hexFromBytes(bytes) {
|