@account-kit/signer 4.21.0 → 4.23.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/esm/base.d.ts +183 -2
- package/dist/esm/base.js +352 -55
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +46 -5
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +51 -4
- package/dist/esm/client/index.js +201 -9
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +102 -1
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/errors.d.ts +6 -0
- package/dist/esm/errors.js +18 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/session/manager.d.ts +2 -0
- package/dist/esm/session/manager.js.map +1 -1
- package/dist/esm/signer.d.ts +4 -2
- package/dist/esm/signer.js.map +1 -1
- package/dist/esm/types.d.ts +15 -1
- package/dist/esm/types.js +6 -0
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/parseMfaError.d.ts +2 -0
- package/dist/esm/utils/parseMfaError.js +15 -0
- package/dist/esm/utils/parseMfaError.js.map +1 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/types/base.d.ts +183 -2
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +46 -5
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +51 -4
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +102 -1
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/session/manager.d.ts +2 -0
- package/dist/types/session/manager.d.ts.map +1 -1
- package/dist/types/signer.d.ts +4 -2
- package/dist/types/signer.d.ts.map +1 -1
- package/dist/types/types.d.ts +15 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/parseMfaError.d.ts +3 -0
- package/dist/types/utils/parseMfaError.d.ts.map +1 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/src/base.ts +421 -62
- package/src/client/base.ts +56 -2
- package/src/client/index.ts +225 -10
- package/src/client/types.ts +112 -1
- package/src/errors.ts +11 -1
- package/src/index.ts +5 -1
- package/src/session/manager.ts +6 -1
- package/src/signer.ts +6 -1
- package/src/types.ts +16 -0
- package/src/utils/parseMfaError.ts +15 -0
- package/src/version.ts +1 -1
package/dist/esm/base.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { type SmartAccountAuthenticator } from "@aa-sdk/core";
|
|
|
2
2
|
import { type GetTransactionType, type Hex, type IsNarrowable, type LocalAccount, type SerializeTransactionFn, type SignableMessage, type TransactionSerializable, type TransactionSerialized, type TypedData, type TypedDataDefinition } from "viem";
|
|
3
3
|
import { type Authorization } from "viem/experimental";
|
|
4
4
|
import type { BaseSignerClient } from "./client/base";
|
|
5
|
-
import type { OauthConfig, User } from "./client/types";
|
|
5
|
+
import type { MfaFactor, OauthConfig, User, VerifyMfaParams, EnableMfaParams, EnableMfaResult, RemoveMfaParams } from "./client/types";
|
|
6
6
|
import { type SessionManagerParams } from "./session/manager.js";
|
|
7
7
|
import type { AuthParams } from "./signer";
|
|
8
8
|
import { SolanaSigner } from "./solanaSigner.js";
|
|
9
|
-
import { type AlchemySignerEvents, type ErrorInfo } from "./types.js";
|
|
9
|
+
import { type AlchemySignerEvents, type ErrorInfo, type ValidateMultiFactorsArgs } from "./types.js";
|
|
10
10
|
export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
|
|
11
11
|
client: TClient;
|
|
12
12
|
sessionConfig?: Omit<SessionManagerParams, "client">;
|
|
@@ -281,6 +281,36 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
|
|
|
281
281
|
* @returns {Promise<Authorization<number, true>> | undefined} a promise that resolves to the authorization with the signature
|
|
282
282
|
*/
|
|
283
283
|
signAuthorization: (unsignedAuthorization: Authorization<number, false>) => Promise<Authorization<number, true>>;
|
|
284
|
+
/**
|
|
285
|
+
* Gets the current MFA status
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
290
|
+
*
|
|
291
|
+
* const signer = new AlchemyWebSigner({
|
|
292
|
+
* client: {
|
|
293
|
+
* connection: {
|
|
294
|
+
* rpcUrl: "/api/rpc",
|
|
295
|
+
* },
|
|
296
|
+
* iframeConfig: {
|
|
297
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
298
|
+
* },
|
|
299
|
+
* },
|
|
300
|
+
* });
|
|
301
|
+
*
|
|
302
|
+
* const mfaStatus = signer.getMfaStatus();
|
|
303
|
+
* if (mfaStatus === AlchemyMfaStatus.REQUIRED) {
|
|
304
|
+
* // Handle MFA requirement
|
|
305
|
+
* }
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @returns {{ mfaRequired: boolean; mfaFactorId?: string }} The current MFA status
|
|
309
|
+
*/
|
|
310
|
+
getMfaStatus: () => {
|
|
311
|
+
mfaRequired: boolean;
|
|
312
|
+
mfaFactorId?: string;
|
|
313
|
+
};
|
|
284
314
|
private unpackSignRawMessageBytes;
|
|
285
315
|
/**
|
|
286
316
|
* Unauthenticated call to look up a user's organizationId by email
|
|
@@ -421,9 +451,160 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
|
|
|
421
451
|
private authenticateWithOauth;
|
|
422
452
|
private authenticateWithOtp;
|
|
423
453
|
private handleOauthReturn;
|
|
454
|
+
private handleMfaRequired;
|
|
424
455
|
private getExpirationSeconds;
|
|
425
456
|
private registerListeners;
|
|
426
457
|
private emitNewUserEvent;
|
|
458
|
+
private initOrCreateEmailUser;
|
|
459
|
+
private completeEmailAuth;
|
|
460
|
+
/**
|
|
461
|
+
* Retrieves the list of MFA factors configured for the current user.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
466
|
+
*
|
|
467
|
+
* const signer = new AlchemyWebSigner({
|
|
468
|
+
* client: {
|
|
469
|
+
* connection: {
|
|
470
|
+
* rpcUrl: "/api/rpc",
|
|
471
|
+
* },
|
|
472
|
+
* iframeConfig: {
|
|
473
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
474
|
+
* },
|
|
475
|
+
* },
|
|
476
|
+
* });
|
|
477
|
+
*
|
|
478
|
+
* const { multiFactors } = await signer.getMfaFactors();
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
482
|
+
* @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
|
|
483
|
+
*/
|
|
484
|
+
getMfaFactors: () => Promise<{
|
|
485
|
+
multiFactors: MfaFactor[];
|
|
486
|
+
}>;
|
|
487
|
+
/**
|
|
488
|
+
* Initiates the setup of a new MFA factor for the current user.
|
|
489
|
+
* The factor will need to be verified using verifyMfa before it becomes active.
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
494
|
+
*
|
|
495
|
+
* const signer = new AlchemyWebSigner({
|
|
496
|
+
* client: {
|
|
497
|
+
* connection: {
|
|
498
|
+
* rpcUrl: "/api/rpc",
|
|
499
|
+
* },
|
|
500
|
+
* iframeConfig: {
|
|
501
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
502
|
+
* },
|
|
503
|
+
* },
|
|
504
|
+
* });
|
|
505
|
+
*
|
|
506
|
+
* const result = await signer.addMfa({ multiFactorType: "totp" });
|
|
507
|
+
* // Result contains multiFactorTotpUrl to display as QR code
|
|
508
|
+
* ```
|
|
509
|
+
*
|
|
510
|
+
* @param {EnableMfaParams} params The parameters required to enable a new MFA factor
|
|
511
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
512
|
+
* @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
|
|
513
|
+
*/
|
|
514
|
+
addMfa: (params: EnableMfaParams) => Promise<EnableMfaResult>;
|
|
515
|
+
/**
|
|
516
|
+
* Verifies a newly created MFA factor to complete the setup process.
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```ts
|
|
520
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
521
|
+
*
|
|
522
|
+
* const signer = new AlchemyWebSigner({
|
|
523
|
+
* client: {
|
|
524
|
+
* connection: {
|
|
525
|
+
* rpcUrl: "/api/rpc",
|
|
526
|
+
* },
|
|
527
|
+
* iframeConfig: {
|
|
528
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
529
|
+
* },
|
|
530
|
+
* },
|
|
531
|
+
* });
|
|
532
|
+
*
|
|
533
|
+
* const result = await signer.verifyMfa({
|
|
534
|
+
* multiFactorId: "factor-id",
|
|
535
|
+
* multiFactorCode: "123456" // 6-digit code from authenticator app
|
|
536
|
+
* });
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* @param {VerifyMfaParams} params The parameters required to verify the MFA factor
|
|
540
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
541
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
542
|
+
*/
|
|
543
|
+
verifyMfa: (params: VerifyMfaParams) => Promise<{
|
|
544
|
+
multiFactors: MfaFactor[];
|
|
545
|
+
}>;
|
|
546
|
+
/**
|
|
547
|
+
* Removes existing MFA factors by their IDs.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```ts
|
|
551
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
552
|
+
*
|
|
553
|
+
* const signer = new AlchemyWebSigner({
|
|
554
|
+
* client: {
|
|
555
|
+
* connection: {
|
|
556
|
+
* rpcUrl: "/api/rpc",
|
|
557
|
+
* },
|
|
558
|
+
* iframeConfig: {
|
|
559
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
560
|
+
* },
|
|
561
|
+
* },
|
|
562
|
+
* });
|
|
563
|
+
*
|
|
564
|
+
* const result = await signer.removeMfa({
|
|
565
|
+
* multiFactorIds: ["factor-id-1", "factor-id-2"]
|
|
566
|
+
* });
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
* @param {RemoveMfaParams} params The parameters specifying which factors to disable
|
|
570
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
571
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
572
|
+
*/
|
|
573
|
+
removeMfa: (params: RemoveMfaParams) => Promise<{
|
|
574
|
+
multiFactors: MfaFactor[];
|
|
575
|
+
}>;
|
|
576
|
+
/**
|
|
577
|
+
* Validates MFA factors that were required during authentication.
|
|
578
|
+
* This function should be called after MFA is required and the user has provided their MFA code.
|
|
579
|
+
* It completes the authentication process by validating the MFA factors and completing the auth bundle.
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```ts
|
|
583
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
584
|
+
*
|
|
585
|
+
* const signer = new AlchemyWebSigner({
|
|
586
|
+
* client: {
|
|
587
|
+
* connection: {
|
|
588
|
+
* rpcUrl: "/api/rpc",
|
|
589
|
+
* },
|
|
590
|
+
* iframeConfig: {
|
|
591
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
592
|
+
* },
|
|
593
|
+
* },
|
|
594
|
+
* });
|
|
595
|
+
*
|
|
596
|
+
* // After MFA is required and user provides code
|
|
597
|
+
* const user = await signer.validateMultiFactors({
|
|
598
|
+
* multiFactorCode: "123456", // 6-digit code from authenticator app
|
|
599
|
+
* multiFactorId: "factor-id"
|
|
600
|
+
* });
|
|
601
|
+
* ```
|
|
602
|
+
*
|
|
603
|
+
* @param {ValidateMultiFactorsArgs} params - Parameters for validating MFA factors
|
|
604
|
+
* @throws {Error} If there is no pending MFA context or if orgId is not found
|
|
605
|
+
* @returns {Promise<User>} A promise that resolves to the authenticated user
|
|
606
|
+
*/
|
|
607
|
+
validateMultiFactors(params: ValidateMultiFactorsArgs): Promise<User>;
|
|
427
608
|
protected initConfig: () => Promise<SignerConfig>;
|
|
428
609
|
/**
|
|
429
610
|
* Returns the signer configuration while fetching it if it's not already initialized.
|
package/dist/esm/base.js
CHANGED
|
@@ -87,6 +87,8 @@ export class BaseAlchemySigner {
|
|
|
87
87
|
if (isNewUser)
|
|
88
88
|
listener();
|
|
89
89
|
}, { fireImmediately: true });
|
|
90
|
+
case "mfaStatusChanged":
|
|
91
|
+
return this.store.subscribe(({ mfaStatus }) => mfaStatus, (mfaStatus) => listener(mfaStatus), { fireImmediately: true });
|
|
90
92
|
default:
|
|
91
93
|
assertNever(event, `Unknown event type ${event}`);
|
|
92
94
|
}
|
|
@@ -483,6 +485,40 @@ export class BaseAlchemySigner {
|
|
|
483
485
|
return { ...unsignedAuthorization, ...signature };
|
|
484
486
|
})
|
|
485
487
|
});
|
|
488
|
+
/**
|
|
489
|
+
* Gets the current MFA status
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
494
|
+
*
|
|
495
|
+
* const signer = new AlchemyWebSigner({
|
|
496
|
+
* client: {
|
|
497
|
+
* connection: {
|
|
498
|
+
* rpcUrl: "/api/rpc",
|
|
499
|
+
* },
|
|
500
|
+
* iframeConfig: {
|
|
501
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
502
|
+
* },
|
|
503
|
+
* },
|
|
504
|
+
* });
|
|
505
|
+
*
|
|
506
|
+
* const mfaStatus = signer.getMfaStatus();
|
|
507
|
+
* if (mfaStatus === AlchemyMfaStatus.REQUIRED) {
|
|
508
|
+
* // Handle MFA requirement
|
|
509
|
+
* }
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @returns {{ mfaRequired: boolean; mfaFactorId?: string }} The current MFA status
|
|
513
|
+
*/
|
|
514
|
+
Object.defineProperty(this, "getMfaStatus", {
|
|
515
|
+
enumerable: true,
|
|
516
|
+
configurable: true,
|
|
517
|
+
writable: true,
|
|
518
|
+
value: () => {
|
|
519
|
+
return this.store.getState().mfaStatus;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
486
522
|
Object.defineProperty(this, "unpackSignRawMessageBytes", {
|
|
487
523
|
enumerable: true,
|
|
488
524
|
configurable: true,
|
|
@@ -691,61 +727,27 @@ export class BaseAlchemySigner {
|
|
|
691
727
|
configurable: true,
|
|
692
728
|
writable: true,
|
|
693
729
|
value: async (params) => {
|
|
694
|
-
if ("
|
|
695
|
-
|
|
696
|
-
const expirationSeconds = this.getExpirationSeconds();
|
|
697
|
-
const { orgId, otpId } = existingUser
|
|
698
|
-
? await this.inner.initEmailAuth({
|
|
699
|
-
email: params.email,
|
|
700
|
-
emailMode: params.emailMode,
|
|
701
|
-
expirationSeconds,
|
|
702
|
-
redirectParams: params.redirectParams,
|
|
703
|
-
})
|
|
704
|
-
: await this.inner.createAccount({
|
|
705
|
-
type: "email",
|
|
706
|
-
email: params.email,
|
|
707
|
-
emailMode: params.emailMode,
|
|
708
|
-
expirationSeconds,
|
|
709
|
-
redirectParams: params.redirectParams,
|
|
710
|
-
});
|
|
711
|
-
this.sessionManager.setTemporarySession({
|
|
712
|
-
orgId,
|
|
713
|
-
isNewUser: !existingUser,
|
|
714
|
-
});
|
|
715
|
-
this.store.setState({
|
|
716
|
-
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
717
|
-
otpId,
|
|
718
|
-
error: null,
|
|
719
|
-
});
|
|
720
|
-
// We wait for the session manager to emit a connected event if
|
|
721
|
-
// cross tab sessions are permitted
|
|
722
|
-
return new Promise((resolve) => {
|
|
723
|
-
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
724
|
-
resolve(session.user);
|
|
725
|
-
removeListener();
|
|
726
|
-
});
|
|
727
|
-
});
|
|
730
|
+
if ("bundle" in params) {
|
|
731
|
+
return this.completeEmailAuth(params);
|
|
728
732
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
733
|
+
const { orgId, otpId, isNewUser } = await this.initOrCreateEmailUser(params.email, params.emailMode, params.multiFactors, params.redirectParams);
|
|
734
|
+
this.sessionManager.setTemporarySession({
|
|
735
|
+
orgId,
|
|
736
|
+
isNewUser,
|
|
737
|
+
});
|
|
738
|
+
this.store.setState({
|
|
739
|
+
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
740
|
+
otpId,
|
|
741
|
+
error: null,
|
|
742
|
+
});
|
|
743
|
+
// We wait for the session manager to emit a connected event if
|
|
744
|
+
// cross tab sessions are permitted
|
|
745
|
+
return new Promise((resolve) => {
|
|
746
|
+
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
747
|
+
resolve(session.user);
|
|
748
|
+
removeListener();
|
|
744
749
|
});
|
|
745
|
-
|
|
746
|
-
this.emitNewUserEvent(params.isNewUser);
|
|
747
|
-
return user;
|
|
748
|
-
}
|
|
750
|
+
});
|
|
749
751
|
}
|
|
750
752
|
});
|
|
751
753
|
Object.defineProperty(this, "authenticateWithPasskey", {
|
|
@@ -814,14 +816,24 @@ export class BaseAlchemySigner {
|
|
|
814
816
|
if (!otpId) {
|
|
815
817
|
throw new Error("otpId not found in session");
|
|
816
818
|
}
|
|
817
|
-
const
|
|
819
|
+
const response = await this.inner.submitOtpCode({
|
|
818
820
|
orgId,
|
|
819
821
|
otpId,
|
|
820
822
|
otpCode: args.otpCode,
|
|
821
823
|
expirationSeconds: this.getExpirationSeconds(),
|
|
824
|
+
multiFactors: args.multiFactors,
|
|
822
825
|
});
|
|
826
|
+
if (response.mfaRequired) {
|
|
827
|
+
this.handleMfaRequired(response.encryptedPayload, response.multiFactors);
|
|
828
|
+
return new Promise((resolve) => {
|
|
829
|
+
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
830
|
+
resolve(session.user);
|
|
831
|
+
removeListener();
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
}
|
|
823
835
|
const user = await this.inner.completeAuthWithBundle({
|
|
824
|
-
bundle,
|
|
836
|
+
bundle: response.bundle,
|
|
825
837
|
orgId,
|
|
826
838
|
connectedEventName: "connectedOtp",
|
|
827
839
|
authenticatingType: "otp",
|
|
@@ -928,6 +940,144 @@ export class BaseAlchemySigner {
|
|
|
928
940
|
this.store.setState({ isNewUser });
|
|
929
941
|
}
|
|
930
942
|
});
|
|
943
|
+
/**
|
|
944
|
+
* Retrieves the list of MFA factors configured for the current user.
|
|
945
|
+
*
|
|
946
|
+
* @example
|
|
947
|
+
* ```ts
|
|
948
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
949
|
+
*
|
|
950
|
+
* const signer = new AlchemyWebSigner({
|
|
951
|
+
* client: {
|
|
952
|
+
* connection: {
|
|
953
|
+
* rpcUrl: "/api/rpc",
|
|
954
|
+
* },
|
|
955
|
+
* iframeConfig: {
|
|
956
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
957
|
+
* },
|
|
958
|
+
* },
|
|
959
|
+
* });
|
|
960
|
+
*
|
|
961
|
+
* const { multiFactors } = await signer.getMfaFactors();
|
|
962
|
+
* ```
|
|
963
|
+
*
|
|
964
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
965
|
+
* @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
|
|
966
|
+
*/
|
|
967
|
+
Object.defineProperty(this, "getMfaFactors", {
|
|
968
|
+
enumerable: true,
|
|
969
|
+
configurable: true,
|
|
970
|
+
writable: true,
|
|
971
|
+
value: SignerLogger.profiled("BaseAlchemySigner.getMfaFactors", async () => {
|
|
972
|
+
return this.inner.getMfaFactors();
|
|
973
|
+
})
|
|
974
|
+
});
|
|
975
|
+
/**
|
|
976
|
+
* Initiates the setup of a new MFA factor for the current user.
|
|
977
|
+
* The factor will need to be verified using verifyMfa before it becomes active.
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```ts
|
|
981
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
982
|
+
*
|
|
983
|
+
* const signer = new AlchemyWebSigner({
|
|
984
|
+
* client: {
|
|
985
|
+
* connection: {
|
|
986
|
+
* rpcUrl: "/api/rpc",
|
|
987
|
+
* },
|
|
988
|
+
* iframeConfig: {
|
|
989
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
990
|
+
* },
|
|
991
|
+
* },
|
|
992
|
+
* });
|
|
993
|
+
*
|
|
994
|
+
* const result = await signer.addMfa({ multiFactorType: "totp" });
|
|
995
|
+
* // Result contains multiFactorTotpUrl to display as QR code
|
|
996
|
+
* ```
|
|
997
|
+
*
|
|
998
|
+
* @param {EnableMfaParams} params The parameters required to enable a new MFA factor
|
|
999
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1000
|
+
* @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
|
|
1001
|
+
*/
|
|
1002
|
+
Object.defineProperty(this, "addMfa", {
|
|
1003
|
+
enumerable: true,
|
|
1004
|
+
configurable: true,
|
|
1005
|
+
writable: true,
|
|
1006
|
+
value: SignerLogger.profiled("BaseAlchemySigner.addMfa", async (params) => {
|
|
1007
|
+
return this.inner.addMfa(params);
|
|
1008
|
+
})
|
|
1009
|
+
});
|
|
1010
|
+
/**
|
|
1011
|
+
* Verifies a newly created MFA factor to complete the setup process.
|
|
1012
|
+
*
|
|
1013
|
+
* @example
|
|
1014
|
+
* ```ts
|
|
1015
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1016
|
+
*
|
|
1017
|
+
* const signer = new AlchemyWebSigner({
|
|
1018
|
+
* client: {
|
|
1019
|
+
* connection: {
|
|
1020
|
+
* rpcUrl: "/api/rpc",
|
|
1021
|
+
* },
|
|
1022
|
+
* iframeConfig: {
|
|
1023
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1024
|
+
* },
|
|
1025
|
+
* },
|
|
1026
|
+
* });
|
|
1027
|
+
*
|
|
1028
|
+
* const result = await signer.verifyMfa({
|
|
1029
|
+
* multiFactorId: "factor-id",
|
|
1030
|
+
* multiFactorCode: "123456" // 6-digit code from authenticator app
|
|
1031
|
+
* });
|
|
1032
|
+
* ```
|
|
1033
|
+
*
|
|
1034
|
+
* @param {VerifyMfaParams} params The parameters required to verify the MFA factor
|
|
1035
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1036
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
1037
|
+
*/
|
|
1038
|
+
Object.defineProperty(this, "verifyMfa", {
|
|
1039
|
+
enumerable: true,
|
|
1040
|
+
configurable: true,
|
|
1041
|
+
writable: true,
|
|
1042
|
+
value: SignerLogger.profiled("BaseAlchemySigner.verifyMfa", async (params) => {
|
|
1043
|
+
return this.inner.verifyMfa(params);
|
|
1044
|
+
})
|
|
1045
|
+
});
|
|
1046
|
+
/**
|
|
1047
|
+
* Removes existing MFA factors by their IDs.
|
|
1048
|
+
*
|
|
1049
|
+
* @example
|
|
1050
|
+
* ```ts
|
|
1051
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1052
|
+
*
|
|
1053
|
+
* const signer = new AlchemyWebSigner({
|
|
1054
|
+
* client: {
|
|
1055
|
+
* connection: {
|
|
1056
|
+
* rpcUrl: "/api/rpc",
|
|
1057
|
+
* },
|
|
1058
|
+
* iframeConfig: {
|
|
1059
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1060
|
+
* },
|
|
1061
|
+
* },
|
|
1062
|
+
* });
|
|
1063
|
+
*
|
|
1064
|
+
* const result = await signer.removeMfa({
|
|
1065
|
+
* multiFactorIds: ["factor-id-1", "factor-id-2"]
|
|
1066
|
+
* });
|
|
1067
|
+
* ```
|
|
1068
|
+
*
|
|
1069
|
+
* @param {RemoveMfaParams} params The parameters specifying which factors to disable
|
|
1070
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1071
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
1072
|
+
*/
|
|
1073
|
+
Object.defineProperty(this, "removeMfa", {
|
|
1074
|
+
enumerable: true,
|
|
1075
|
+
configurable: true,
|
|
1076
|
+
writable: true,
|
|
1077
|
+
value: SignerLogger.profiled("BaseAlchemySigner.removeMfa", async (params) => {
|
|
1078
|
+
return this.inner.removeMfa(params);
|
|
1079
|
+
})
|
|
1080
|
+
});
|
|
931
1081
|
Object.defineProperty(this, "initConfig", {
|
|
932
1082
|
enumerable: true,
|
|
933
1083
|
configurable: true,
|
|
@@ -966,6 +1116,10 @@ export class BaseAlchemySigner {
|
|
|
966
1116
|
user: null,
|
|
967
1117
|
status: AlchemySignerStatus.INITIALIZING,
|
|
968
1118
|
error: initialError ?? null,
|
|
1119
|
+
mfaStatus: {
|
|
1120
|
+
mfaRequired: false,
|
|
1121
|
+
mfaFactorId: undefined,
|
|
1122
|
+
},
|
|
969
1123
|
})));
|
|
970
1124
|
// NOTE: it's important that the session manager share a client
|
|
971
1125
|
// with the signer. The SessionManager leverages the Signer's client
|
|
@@ -980,6 +1134,149 @@ export class BaseAlchemySigner {
|
|
|
980
1134
|
this.sessionManager.initialize();
|
|
981
1135
|
this.config = this.fetchConfig();
|
|
982
1136
|
}
|
|
1137
|
+
handleMfaRequired(encryptedPayload, multiFactors) {
|
|
1138
|
+
// Store complete MFA context in the temporary session
|
|
1139
|
+
const tempSession = this.sessionManager.getTemporarySession();
|
|
1140
|
+
if (tempSession) {
|
|
1141
|
+
this.sessionManager.setTemporarySession({
|
|
1142
|
+
...tempSession,
|
|
1143
|
+
encryptedPayload,
|
|
1144
|
+
mfaFactorId: multiFactors?.[0]?.multiFactorId,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
// Keep minimal state in the store for UI updates
|
|
1148
|
+
this.store.setState({
|
|
1149
|
+
status: AlchemySignerStatus.AWAITING_MFA_AUTH,
|
|
1150
|
+
error: null,
|
|
1151
|
+
mfaStatus: {
|
|
1152
|
+
mfaRequired: true,
|
|
1153
|
+
mfaFactorId: multiFactors?.[0]?.multiFactorId,
|
|
1154
|
+
},
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
async initOrCreateEmailUser(email, emailMode, multiFactors, redirectParams) {
|
|
1158
|
+
const existingUser = await this.getUser(email);
|
|
1159
|
+
const expirationSeconds = this.getExpirationSeconds();
|
|
1160
|
+
if (existingUser) {
|
|
1161
|
+
const { orgId, otpId } = await this.inner.initEmailAuth({
|
|
1162
|
+
email: email,
|
|
1163
|
+
emailMode: emailMode,
|
|
1164
|
+
expirationSeconds,
|
|
1165
|
+
redirectParams: redirectParams,
|
|
1166
|
+
multiFactors,
|
|
1167
|
+
});
|
|
1168
|
+
return {
|
|
1169
|
+
orgId,
|
|
1170
|
+
otpId,
|
|
1171
|
+
isNewUser: false,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const { orgId, otpId } = await this.inner.createAccount({
|
|
1175
|
+
type: "email",
|
|
1176
|
+
email,
|
|
1177
|
+
emailMode,
|
|
1178
|
+
expirationSeconds,
|
|
1179
|
+
redirectParams,
|
|
1180
|
+
});
|
|
1181
|
+
return {
|
|
1182
|
+
orgId,
|
|
1183
|
+
otpId,
|
|
1184
|
+
isNewUser: true,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
async completeEmailAuth(params) {
|
|
1188
|
+
const temporarySession = params.orgId
|
|
1189
|
+
? { orgId: params.orgId }
|
|
1190
|
+
: this.sessionManager.getTemporarySession();
|
|
1191
|
+
if (!temporarySession) {
|
|
1192
|
+
this.store.setState({ status: AlchemySignerStatus.DISCONNECTED });
|
|
1193
|
+
throw new Error("Could not find email auth init session!");
|
|
1194
|
+
}
|
|
1195
|
+
const user = await this.inner.completeAuthWithBundle({
|
|
1196
|
+
bundle: params.bundle,
|
|
1197
|
+
orgId: temporarySession.orgId,
|
|
1198
|
+
connectedEventName: "connectedEmail",
|
|
1199
|
+
authenticatingType: "email",
|
|
1200
|
+
});
|
|
1201
|
+
// fire new user event
|
|
1202
|
+
this.emitNewUserEvent(params.isNewUser);
|
|
1203
|
+
return user;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Validates MFA factors that were required during authentication.
|
|
1207
|
+
* This function should be called after MFA is required and the user has provided their MFA code.
|
|
1208
|
+
* It completes the authentication process by validating the MFA factors and completing the auth bundle.
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```ts
|
|
1212
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1213
|
+
*
|
|
1214
|
+
* const signer = new AlchemyWebSigner({
|
|
1215
|
+
* client: {
|
|
1216
|
+
* connection: {
|
|
1217
|
+
* rpcUrl: "/api/rpc",
|
|
1218
|
+
* },
|
|
1219
|
+
* iframeConfig: {
|
|
1220
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1221
|
+
* },
|
|
1222
|
+
* },
|
|
1223
|
+
* });
|
|
1224
|
+
*
|
|
1225
|
+
* // After MFA is required and user provides code
|
|
1226
|
+
* const user = await signer.validateMultiFactors({
|
|
1227
|
+
* multiFactorCode: "123456", // 6-digit code from authenticator app
|
|
1228
|
+
* multiFactorId: "factor-id"
|
|
1229
|
+
* });
|
|
1230
|
+
* ```
|
|
1231
|
+
*
|
|
1232
|
+
* @param {ValidateMultiFactorsArgs} params - Parameters for validating MFA factors
|
|
1233
|
+
* @throws {Error} If there is no pending MFA context or if orgId is not found
|
|
1234
|
+
* @returns {Promise<User>} A promise that resolves to the authenticated user
|
|
1235
|
+
*/
|
|
1236
|
+
async validateMultiFactors(params) {
|
|
1237
|
+
// Get MFA context from temporary session
|
|
1238
|
+
const tempSession = this.sessionManager.getTemporarySession();
|
|
1239
|
+
if (!tempSession?.orgId ||
|
|
1240
|
+
!tempSession.encryptedPayload ||
|
|
1241
|
+
!tempSession.mfaFactorId) {
|
|
1242
|
+
throw new Error("No pending MFA context found. Call submitOtpCode() first.");
|
|
1243
|
+
}
|
|
1244
|
+
if (params.multiFactorId &&
|
|
1245
|
+
tempSession.mfaFactorId !== params.multiFactorId) {
|
|
1246
|
+
throw new Error("MFA factor ID mismatch");
|
|
1247
|
+
}
|
|
1248
|
+
// Call the low-level client
|
|
1249
|
+
const { bundle } = await this.inner.validateMultiFactors({
|
|
1250
|
+
encryptedPayload: tempSession.encryptedPayload,
|
|
1251
|
+
multiFactors: [
|
|
1252
|
+
{
|
|
1253
|
+
multiFactorId: tempSession.mfaFactorId,
|
|
1254
|
+
multiFactorCode: params.multiFactorCode,
|
|
1255
|
+
},
|
|
1256
|
+
],
|
|
1257
|
+
});
|
|
1258
|
+
// Complete the authentication
|
|
1259
|
+
const user = await this.inner.completeAuthWithBundle({
|
|
1260
|
+
bundle,
|
|
1261
|
+
orgId: tempSession.orgId,
|
|
1262
|
+
connectedEventName: "connectedOtp",
|
|
1263
|
+
authenticatingType: "otp",
|
|
1264
|
+
});
|
|
1265
|
+
// Remove MFA data from temporary session
|
|
1266
|
+
this.sessionManager.setTemporarySession({
|
|
1267
|
+
...tempSession,
|
|
1268
|
+
encryptedPayload: undefined,
|
|
1269
|
+
mfaFactorId: undefined,
|
|
1270
|
+
});
|
|
1271
|
+
// Update UI state
|
|
1272
|
+
this.store.setState({
|
|
1273
|
+
mfaStatus: {
|
|
1274
|
+
mfaRequired: false,
|
|
1275
|
+
mfaFactorId: undefined,
|
|
1276
|
+
},
|
|
1277
|
+
});
|
|
1278
|
+
return user;
|
|
1279
|
+
}
|
|
983
1280
|
}
|
|
984
1281
|
function toErrorInfo(error) {
|
|
985
1282
|
return error instanceof Error
|