@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/src/base.ts
CHANGED
|
@@ -21,7 +21,17 @@ import type { Mutate, StoreApi } from "zustand";
|
|
|
21
21
|
import { subscribeWithSelector } from "zustand/middleware";
|
|
22
22
|
import { createStore } from "zustand/vanilla";
|
|
23
23
|
import type { BaseSignerClient } from "./client/base";
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
EmailType,
|
|
26
|
+
MfaFactor,
|
|
27
|
+
OauthConfig,
|
|
28
|
+
OauthParams,
|
|
29
|
+
User,
|
|
30
|
+
VerifyMfaParams,
|
|
31
|
+
EnableMfaParams,
|
|
32
|
+
EnableMfaResult,
|
|
33
|
+
RemoveMfaParams,
|
|
34
|
+
} from "./client/types";
|
|
25
35
|
import { NotAuthenticatedError } from "./errors.js";
|
|
26
36
|
import { SignerLogger } from "./metrics.js";
|
|
27
37
|
import {
|
|
@@ -36,6 +46,7 @@ import {
|
|
|
36
46
|
type AlchemySignerEvent,
|
|
37
47
|
type AlchemySignerEvents,
|
|
38
48
|
type ErrorInfo,
|
|
49
|
+
type ValidateMultiFactorsArgs,
|
|
39
50
|
} from "./types.js";
|
|
40
51
|
import { assertNever } from "./utils/typeAssertions.js";
|
|
41
52
|
|
|
@@ -51,6 +62,11 @@ type AlchemySignerStore = {
|
|
|
51
62
|
error: ErrorInfo | null;
|
|
52
63
|
otpId?: string;
|
|
53
64
|
isNewUser?: boolean;
|
|
65
|
+
mfaStatus: {
|
|
66
|
+
mfaRequired: boolean;
|
|
67
|
+
mfaFactorId?: string;
|
|
68
|
+
encryptedPayload?: string;
|
|
69
|
+
};
|
|
54
70
|
};
|
|
55
71
|
|
|
56
72
|
type UnpackedSignature = {
|
|
@@ -108,6 +124,10 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
108
124
|
user: null,
|
|
109
125
|
status: AlchemySignerStatus.INITIALIZING,
|
|
110
126
|
error: initialError ?? null,
|
|
127
|
+
mfaStatus: {
|
|
128
|
+
mfaRequired: false,
|
|
129
|
+
mfaFactorId: undefined,
|
|
130
|
+
},
|
|
111
131
|
} satisfies AlchemySignerStore)
|
|
112
132
|
)
|
|
113
133
|
);
|
|
@@ -181,6 +201,13 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
181
201
|
},
|
|
182
202
|
{ fireImmediately: true }
|
|
183
203
|
);
|
|
204
|
+
case "mfaStatusChanged":
|
|
205
|
+
return this.store.subscribe(
|
|
206
|
+
({ mfaStatus }) => mfaStatus,
|
|
207
|
+
(mfaStatus) =>
|
|
208
|
+
(listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus),
|
|
209
|
+
{ fireImmediately: true }
|
|
210
|
+
);
|
|
184
211
|
default:
|
|
185
212
|
assertNever(event, `Unknown event type ${event}`);
|
|
186
213
|
}
|
|
@@ -590,6 +617,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
590
617
|
}
|
|
591
618
|
);
|
|
592
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Gets the current MFA status
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```ts
|
|
625
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
626
|
+
*
|
|
627
|
+
* const signer = new AlchemyWebSigner({
|
|
628
|
+
* client: {
|
|
629
|
+
* connection: {
|
|
630
|
+
* rpcUrl: "/api/rpc",
|
|
631
|
+
* },
|
|
632
|
+
* iframeConfig: {
|
|
633
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
634
|
+
* },
|
|
635
|
+
* },
|
|
636
|
+
* });
|
|
637
|
+
*
|
|
638
|
+
* const mfaStatus = signer.getMfaStatus();
|
|
639
|
+
* if (mfaStatus === AlchemyMfaStatus.REQUIRED) {
|
|
640
|
+
* // Handle MFA requirement
|
|
641
|
+
* }
|
|
642
|
+
* ```
|
|
643
|
+
*
|
|
644
|
+
* @returns {{ mfaRequired: boolean; mfaFactorId?: string }} The current MFA status
|
|
645
|
+
*/
|
|
646
|
+
getMfaStatus = (): {
|
|
647
|
+
mfaRequired: boolean;
|
|
648
|
+
mfaFactorId?: string;
|
|
649
|
+
} => {
|
|
650
|
+
return this.store.getState().mfaStatus;
|
|
651
|
+
};
|
|
652
|
+
|
|
593
653
|
private unpackSignRawMessageBytes = (
|
|
594
654
|
hex: `0x${string}`
|
|
595
655
|
): UnpackedSignature => {
|
|
@@ -784,70 +844,36 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
784
844
|
private authenticateWithEmail = async (
|
|
785
845
|
params: Extract<AuthParams, { type: "email" }>
|
|
786
846
|
): Promise<User> => {
|
|
787
|
-
if ("
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const { orgId, otpId } = existingUser
|
|
792
|
-
? await this.inner.initEmailAuth({
|
|
793
|
-
email: params.email,
|
|
794
|
-
emailMode: params.emailMode,
|
|
795
|
-
expirationSeconds,
|
|
796
|
-
redirectParams: params.redirectParams,
|
|
797
|
-
})
|
|
798
|
-
: await this.inner.createAccount({
|
|
799
|
-
type: "email",
|
|
800
|
-
email: params.email,
|
|
801
|
-
emailMode: params.emailMode,
|
|
802
|
-
expirationSeconds,
|
|
803
|
-
redirectParams: params.redirectParams,
|
|
804
|
-
});
|
|
847
|
+
if ("bundle" in params) {
|
|
848
|
+
return this.completeEmailAuth(params);
|
|
849
|
+
}
|
|
805
850
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
otpId,
|
|
813
|
-
error: null,
|
|
814
|
-
});
|
|
851
|
+
const { orgId, otpId, isNewUser } = await this.initOrCreateEmailUser(
|
|
852
|
+
params.email,
|
|
853
|
+
params.emailMode,
|
|
854
|
+
params.multiFactors,
|
|
855
|
+
params.redirectParams
|
|
856
|
+
);
|
|
815
857
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
"connected",
|
|
821
|
-
(session) => {
|
|
822
|
-
resolve(session.user);
|
|
823
|
-
removeListener();
|
|
824
|
-
}
|
|
825
|
-
);
|
|
826
|
-
});
|
|
827
|
-
} else {
|
|
828
|
-
const temporarySession = params.orgId
|
|
829
|
-
? { orgId: params.orgId }
|
|
830
|
-
: this.sessionManager.getTemporarySession();
|
|
858
|
+
this.sessionManager.setTemporarySession({
|
|
859
|
+
orgId,
|
|
860
|
+
isNewUser,
|
|
861
|
+
});
|
|
831
862
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
}
|
|
863
|
+
this.store.setState({
|
|
864
|
+
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
865
|
+
otpId,
|
|
866
|
+
error: null,
|
|
867
|
+
});
|
|
838
868
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
869
|
+
// We wait for the session manager to emit a connected event if
|
|
870
|
+
// cross tab sessions are permitted
|
|
871
|
+
return new Promise<User>((resolve) => {
|
|
872
|
+
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
873
|
+
resolve(session.user);
|
|
874
|
+
removeListener();
|
|
844
875
|
});
|
|
845
|
-
|
|
846
|
-
// fire new user event
|
|
847
|
-
this.emitNewUserEvent(params.isNewUser);
|
|
848
|
-
|
|
849
|
-
return user;
|
|
850
|
-
}
|
|
876
|
+
});
|
|
851
877
|
};
|
|
852
878
|
|
|
853
879
|
private authenticateWithPasskey = async (
|
|
@@ -916,14 +942,31 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
916
942
|
if (!otpId) {
|
|
917
943
|
throw new Error("otpId not found in session");
|
|
918
944
|
}
|
|
919
|
-
|
|
945
|
+
|
|
946
|
+
const response = await this.inner.submitOtpCode({
|
|
920
947
|
orgId,
|
|
921
948
|
otpId,
|
|
922
949
|
otpCode: args.otpCode,
|
|
923
950
|
expirationSeconds: this.getExpirationSeconds(),
|
|
951
|
+
multiFactors: args.multiFactors,
|
|
924
952
|
});
|
|
953
|
+
|
|
954
|
+
if (response.mfaRequired) {
|
|
955
|
+
this.handleMfaRequired(response.encryptedPayload, response.multiFactors);
|
|
956
|
+
|
|
957
|
+
return new Promise<User>((resolve) => {
|
|
958
|
+
const removeListener = this.sessionManager.on(
|
|
959
|
+
"connected",
|
|
960
|
+
(session) => {
|
|
961
|
+
resolve(session.user);
|
|
962
|
+
removeListener();
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
925
968
|
const user = await this.inner.completeAuthWithBundle({
|
|
926
|
-
bundle,
|
|
969
|
+
bundle: response.bundle,
|
|
927
970
|
orgId,
|
|
928
971
|
connectedEventName: "connectedOtp",
|
|
929
972
|
authenticatingType: "otp",
|
|
@@ -959,6 +1002,31 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
959
1002
|
return user;
|
|
960
1003
|
};
|
|
961
1004
|
|
|
1005
|
+
private handleMfaRequired(
|
|
1006
|
+
encryptedPayload: string,
|
|
1007
|
+
multiFactors: MfaFactor[]
|
|
1008
|
+
) {
|
|
1009
|
+
// Store complete MFA context in the temporary session
|
|
1010
|
+
const tempSession = this.sessionManager.getTemporarySession();
|
|
1011
|
+
if (tempSession) {
|
|
1012
|
+
this.sessionManager.setTemporarySession({
|
|
1013
|
+
...tempSession,
|
|
1014
|
+
encryptedPayload,
|
|
1015
|
+
mfaFactorId: multiFactors?.[0]?.multiFactorId,
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Keep minimal state in the store for UI updates
|
|
1020
|
+
this.store.setState({
|
|
1021
|
+
status: AlchemySignerStatus.AWAITING_MFA_AUTH,
|
|
1022
|
+
error: null,
|
|
1023
|
+
mfaStatus: {
|
|
1024
|
+
mfaRequired: true,
|
|
1025
|
+
mfaFactorId: multiFactors?.[0]?.multiFactorId,
|
|
1026
|
+
},
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
962
1030
|
private getExpirationSeconds = () =>
|
|
963
1031
|
Math.floor(this.sessionManager.expirationTimeMs / 1000);
|
|
964
1032
|
|
|
@@ -1027,6 +1095,297 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
1027
1095
|
if (isNewUser) this.store.setState({ isNewUser });
|
|
1028
1096
|
};
|
|
1029
1097
|
|
|
1098
|
+
private async initOrCreateEmailUser(
|
|
1099
|
+
email: string,
|
|
1100
|
+
emailMode?: EmailType,
|
|
1101
|
+
multiFactors?: VerifyMfaParams[],
|
|
1102
|
+
redirectParams?: URLSearchParams
|
|
1103
|
+
): Promise<{
|
|
1104
|
+
orgId: string;
|
|
1105
|
+
otpId?: string;
|
|
1106
|
+
isNewUser: boolean;
|
|
1107
|
+
}> {
|
|
1108
|
+
const existingUser = await this.getUser(email);
|
|
1109
|
+
const expirationSeconds = this.getExpirationSeconds();
|
|
1110
|
+
|
|
1111
|
+
if (existingUser) {
|
|
1112
|
+
const { orgId, otpId } = await this.inner.initEmailAuth({
|
|
1113
|
+
email: email,
|
|
1114
|
+
emailMode: emailMode,
|
|
1115
|
+
expirationSeconds,
|
|
1116
|
+
redirectParams: redirectParams,
|
|
1117
|
+
multiFactors,
|
|
1118
|
+
});
|
|
1119
|
+
return {
|
|
1120
|
+
orgId,
|
|
1121
|
+
otpId,
|
|
1122
|
+
isNewUser: false,
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const { orgId, otpId } = await this.inner.createAccount({
|
|
1127
|
+
type: "email",
|
|
1128
|
+
email,
|
|
1129
|
+
emailMode,
|
|
1130
|
+
expirationSeconds,
|
|
1131
|
+
redirectParams,
|
|
1132
|
+
});
|
|
1133
|
+
return {
|
|
1134
|
+
orgId,
|
|
1135
|
+
otpId,
|
|
1136
|
+
isNewUser: true,
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
private async completeEmailAuth(
|
|
1141
|
+
params: Extract<AuthParams, { type: "email"; bundle: string }>
|
|
1142
|
+
): Promise<User> {
|
|
1143
|
+
const temporarySession = params.orgId
|
|
1144
|
+
? { orgId: params.orgId }
|
|
1145
|
+
: this.sessionManager.getTemporarySession();
|
|
1146
|
+
|
|
1147
|
+
if (!temporarySession) {
|
|
1148
|
+
this.store.setState({ status: AlchemySignerStatus.DISCONNECTED });
|
|
1149
|
+
throw new Error("Could not find email auth init session!");
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const user = await this.inner.completeAuthWithBundle({
|
|
1153
|
+
bundle: params.bundle,
|
|
1154
|
+
orgId: temporarySession.orgId,
|
|
1155
|
+
connectedEventName: "connectedEmail",
|
|
1156
|
+
authenticatingType: "email",
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// fire new user event
|
|
1160
|
+
this.emitNewUserEvent(params.isNewUser);
|
|
1161
|
+
|
|
1162
|
+
return user;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Retrieves the list of MFA factors configured for the current user.
|
|
1167
|
+
*
|
|
1168
|
+
* @example
|
|
1169
|
+
* ```ts
|
|
1170
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1171
|
+
*
|
|
1172
|
+
* const signer = new AlchemyWebSigner({
|
|
1173
|
+
* client: {
|
|
1174
|
+
* connection: {
|
|
1175
|
+
* rpcUrl: "/api/rpc",
|
|
1176
|
+
* },
|
|
1177
|
+
* iframeConfig: {
|
|
1178
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1179
|
+
* },
|
|
1180
|
+
* },
|
|
1181
|
+
* });
|
|
1182
|
+
*
|
|
1183
|
+
* const { multiFactors } = await signer.getMfaFactors();
|
|
1184
|
+
* ```
|
|
1185
|
+
*
|
|
1186
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1187
|
+
* @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
|
|
1188
|
+
*/
|
|
1189
|
+
getMfaFactors: () => Promise<{ multiFactors: MfaFactor[] }> =
|
|
1190
|
+
SignerLogger.profiled("BaseAlchemySigner.getMfaFactors", async () => {
|
|
1191
|
+
return this.inner.getMfaFactors();
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Initiates the setup of a new MFA factor for the current user.
|
|
1196
|
+
* The factor will need to be verified using verifyMfa before it becomes active.
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* ```ts
|
|
1200
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1201
|
+
*
|
|
1202
|
+
* const signer = new AlchemyWebSigner({
|
|
1203
|
+
* client: {
|
|
1204
|
+
* connection: {
|
|
1205
|
+
* rpcUrl: "/api/rpc",
|
|
1206
|
+
* },
|
|
1207
|
+
* iframeConfig: {
|
|
1208
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1209
|
+
* },
|
|
1210
|
+
* },
|
|
1211
|
+
* });
|
|
1212
|
+
*
|
|
1213
|
+
* const result = await signer.addMfa({ multiFactorType: "totp" });
|
|
1214
|
+
* // Result contains multiFactorTotpUrl to display as QR code
|
|
1215
|
+
* ```
|
|
1216
|
+
*
|
|
1217
|
+
* @param {EnableMfaParams} params The parameters required to enable a new MFA factor
|
|
1218
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1219
|
+
* @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
|
|
1220
|
+
*/
|
|
1221
|
+
addMfa: (params: EnableMfaParams) => Promise<EnableMfaResult> =
|
|
1222
|
+
SignerLogger.profiled("BaseAlchemySigner.addMfa", async (params) => {
|
|
1223
|
+
return this.inner.addMfa(params);
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Verifies a newly created MFA factor to complete the setup process.
|
|
1228
|
+
*
|
|
1229
|
+
* @example
|
|
1230
|
+
* ```ts
|
|
1231
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1232
|
+
*
|
|
1233
|
+
* const signer = new AlchemyWebSigner({
|
|
1234
|
+
* client: {
|
|
1235
|
+
* connection: {
|
|
1236
|
+
* rpcUrl: "/api/rpc",
|
|
1237
|
+
* },
|
|
1238
|
+
* iframeConfig: {
|
|
1239
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1240
|
+
* },
|
|
1241
|
+
* },
|
|
1242
|
+
* });
|
|
1243
|
+
*
|
|
1244
|
+
* const result = await signer.verifyMfa({
|
|
1245
|
+
* multiFactorId: "factor-id",
|
|
1246
|
+
* multiFactorCode: "123456" // 6-digit code from authenticator app
|
|
1247
|
+
* });
|
|
1248
|
+
* ```
|
|
1249
|
+
*
|
|
1250
|
+
* @param {VerifyMfaParams} params The parameters required to verify the MFA factor
|
|
1251
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1252
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
1253
|
+
*/
|
|
1254
|
+
verifyMfa: (
|
|
1255
|
+
params: VerifyMfaParams
|
|
1256
|
+
) => Promise<{ multiFactors: MfaFactor[] }> = SignerLogger.profiled(
|
|
1257
|
+
"BaseAlchemySigner.verifyMfa",
|
|
1258
|
+
async (params) => {
|
|
1259
|
+
return this.inner.verifyMfa(params);
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Removes existing MFA factors by their IDs.
|
|
1265
|
+
*
|
|
1266
|
+
* @example
|
|
1267
|
+
* ```ts
|
|
1268
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1269
|
+
*
|
|
1270
|
+
* const signer = new AlchemyWebSigner({
|
|
1271
|
+
* client: {
|
|
1272
|
+
* connection: {
|
|
1273
|
+
* rpcUrl: "/api/rpc",
|
|
1274
|
+
* },
|
|
1275
|
+
* iframeConfig: {
|
|
1276
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1277
|
+
* },
|
|
1278
|
+
* },
|
|
1279
|
+
* });
|
|
1280
|
+
*
|
|
1281
|
+
* const result = await signer.removeMfa({
|
|
1282
|
+
* multiFactorIds: ["factor-id-1", "factor-id-2"]
|
|
1283
|
+
* });
|
|
1284
|
+
* ```
|
|
1285
|
+
*
|
|
1286
|
+
* @param {RemoveMfaParams} params The parameters specifying which factors to disable
|
|
1287
|
+
* @throws {NotAuthenticatedError} If no user is authenticated
|
|
1288
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
1289
|
+
*/
|
|
1290
|
+
removeMfa: (
|
|
1291
|
+
params: RemoveMfaParams
|
|
1292
|
+
) => Promise<{ multiFactors: MfaFactor[] }> = SignerLogger.profiled(
|
|
1293
|
+
"BaseAlchemySigner.removeMfa",
|
|
1294
|
+
async (params) => {
|
|
1295
|
+
return this.inner.removeMfa(params);
|
|
1296
|
+
}
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Validates MFA factors that were required during authentication.
|
|
1301
|
+
* This function should be called after MFA is required and the user has provided their MFA code.
|
|
1302
|
+
* It completes the authentication process by validating the MFA factors and completing the auth bundle.
|
|
1303
|
+
*
|
|
1304
|
+
* @example
|
|
1305
|
+
* ```ts
|
|
1306
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
1307
|
+
*
|
|
1308
|
+
* const signer = new AlchemyWebSigner({
|
|
1309
|
+
* client: {
|
|
1310
|
+
* connection: {
|
|
1311
|
+
* rpcUrl: "/api/rpc",
|
|
1312
|
+
* },
|
|
1313
|
+
* iframeConfig: {
|
|
1314
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
1315
|
+
* },
|
|
1316
|
+
* },
|
|
1317
|
+
* });
|
|
1318
|
+
*
|
|
1319
|
+
* // After MFA is required and user provides code
|
|
1320
|
+
* const user = await signer.validateMultiFactors({
|
|
1321
|
+
* multiFactorCode: "123456", // 6-digit code from authenticator app
|
|
1322
|
+
* multiFactorId: "factor-id"
|
|
1323
|
+
* });
|
|
1324
|
+
* ```
|
|
1325
|
+
*
|
|
1326
|
+
* @param {ValidateMultiFactorsArgs} params - Parameters for validating MFA factors
|
|
1327
|
+
* @throws {Error} If there is no pending MFA context or if orgId is not found
|
|
1328
|
+
* @returns {Promise<User>} A promise that resolves to the authenticated user
|
|
1329
|
+
*/
|
|
1330
|
+
public async validateMultiFactors(
|
|
1331
|
+
params: ValidateMultiFactorsArgs
|
|
1332
|
+
): Promise<User> {
|
|
1333
|
+
// Get MFA context from temporary session
|
|
1334
|
+
const tempSession = this.sessionManager.getTemporarySession();
|
|
1335
|
+
if (
|
|
1336
|
+
!tempSession?.orgId ||
|
|
1337
|
+
!tempSession.encryptedPayload ||
|
|
1338
|
+
!tempSession.mfaFactorId
|
|
1339
|
+
) {
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
"No pending MFA context found. Call submitOtpCode() first."
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (
|
|
1346
|
+
params.multiFactorId &&
|
|
1347
|
+
tempSession.mfaFactorId !== params.multiFactorId
|
|
1348
|
+
) {
|
|
1349
|
+
throw new Error("MFA factor ID mismatch");
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Call the low-level client
|
|
1353
|
+
const { bundle } = await this.inner.validateMultiFactors({
|
|
1354
|
+
encryptedPayload: tempSession.encryptedPayload,
|
|
1355
|
+
multiFactors: [
|
|
1356
|
+
{
|
|
1357
|
+
multiFactorId: tempSession.mfaFactorId,
|
|
1358
|
+
multiFactorCode: params.multiFactorCode,
|
|
1359
|
+
},
|
|
1360
|
+
],
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
// Complete the authentication
|
|
1364
|
+
const user = await this.inner.completeAuthWithBundle({
|
|
1365
|
+
bundle,
|
|
1366
|
+
orgId: tempSession.orgId,
|
|
1367
|
+
connectedEventName: "connectedOtp",
|
|
1368
|
+
authenticatingType: "otp",
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
// Remove MFA data from temporary session
|
|
1372
|
+
this.sessionManager.setTemporarySession({
|
|
1373
|
+
...tempSession,
|
|
1374
|
+
encryptedPayload: undefined,
|
|
1375
|
+
mfaFactorId: undefined,
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
// Update UI state
|
|
1379
|
+
this.store.setState({
|
|
1380
|
+
mfaStatus: {
|
|
1381
|
+
mfaRequired: false,
|
|
1382
|
+
mfaFactorId: undefined,
|
|
1383
|
+
},
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
return user;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1030
1389
|
protected initConfig = async (): Promise<SignerConfig> => {
|
|
1031
1390
|
this.config = this.fetchConfig();
|
|
1032
1391
|
return this.config;
|
package/src/client/base.ts
CHANGED
|
@@ -14,10 +14,14 @@ import type {
|
|
|
14
14
|
AlchemySignerClientEvents,
|
|
15
15
|
AuthenticatingEventMetadata,
|
|
16
16
|
CreateAccountParams,
|
|
17
|
+
RemoveMfaParams,
|
|
17
18
|
EmailAuthParams,
|
|
19
|
+
EnableMfaParams,
|
|
20
|
+
EnableMfaResult,
|
|
18
21
|
experimental_CreateApiKeyParams,
|
|
19
22
|
GetOauthProviderUrlArgs,
|
|
20
23
|
GetWebAuthnAttestationResult,
|
|
24
|
+
MfaFactor,
|
|
21
25
|
OauthConfig,
|
|
22
26
|
OauthParams,
|
|
23
27
|
OauthState,
|
|
@@ -27,6 +31,9 @@ import type {
|
|
|
27
31
|
SignerRoutes,
|
|
28
32
|
SignupResponse,
|
|
29
33
|
User,
|
|
34
|
+
VerifyMfaParams,
|
|
35
|
+
SubmitOtpCodeResponse,
|
|
36
|
+
ValidateMultiFactorsParams,
|
|
30
37
|
} from "./types.js";
|
|
31
38
|
import { VERSION } from "../version.js";
|
|
32
39
|
|
|
@@ -133,7 +140,54 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
133
140
|
|
|
134
141
|
public abstract initEmailAuth(
|
|
135
142
|
params: Omit<EmailAuthParams, "targetPublicKey">
|
|
136
|
-
): Promise<{ orgId: string; otpId?: string }>;
|
|
143
|
+
): Promise<{ orgId: string; otpId?: string; multiFactors?: MfaFactor[] }>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Retrieves the list of MFA factors configured for the current user.
|
|
147
|
+
*
|
|
148
|
+
* @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
|
|
149
|
+
*/
|
|
150
|
+
public abstract getMfaFactors(): Promise<{
|
|
151
|
+
multiFactors: MfaFactor[];
|
|
152
|
+
}>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Initiates the setup of a new MFA factor for the current user. Mfa will need to be verified before it is active.
|
|
156
|
+
*
|
|
157
|
+
* @param {EnableMfaParams} params The parameters required to enable a new MFA factor
|
|
158
|
+
* @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
|
|
159
|
+
*/
|
|
160
|
+
public abstract addMfa(params: EnableMfaParams): Promise<EnableMfaResult>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Verifies a newly created MFA factor to complete the setup process.
|
|
164
|
+
*
|
|
165
|
+
* @param {VerifyMfaParams} params The parameters required to verify the MFA factor
|
|
166
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
167
|
+
*/
|
|
168
|
+
public abstract verifyMfa(params: VerifyMfaParams): Promise<{
|
|
169
|
+
multiFactors: MfaFactor[];
|
|
170
|
+
}>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Removes existing MFA factors by ID or factor type.
|
|
174
|
+
*
|
|
175
|
+
* @param {RemoveMfaParams} params The parameters specifying which factors to disable
|
|
176
|
+
* @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
|
|
177
|
+
*/
|
|
178
|
+
public abstract removeMfa(params: RemoveMfaParams): Promise<{
|
|
179
|
+
multiFactors: MfaFactor[];
|
|
180
|
+
}>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validates multiple MFA factors using the provided encrypted payload and MFA codes.
|
|
184
|
+
*
|
|
185
|
+
* @param {ValidateMultiFactorsParams} params The validation parameters
|
|
186
|
+
* @returns {Promise<{ bundle: string }>} A promise that resolves to an object containing the credential bundle
|
|
187
|
+
*/
|
|
188
|
+
public abstract validateMultiFactors(
|
|
189
|
+
params: ValidateMultiFactorsParams
|
|
190
|
+
): Promise<{ bundle: string }>;
|
|
137
191
|
|
|
138
192
|
public abstract completeAuthWithBundle(params: {
|
|
139
193
|
bundle: string;
|
|
@@ -153,7 +207,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
153
207
|
|
|
154
208
|
public abstract submitOtpCode(
|
|
155
209
|
args: Omit<OtpParams, "targetPublicKey">
|
|
156
|
-
): Promise<
|
|
210
|
+
): Promise<SubmitOtpCodeResponse>;
|
|
157
211
|
|
|
158
212
|
public abstract disconnect(): Promise<void>;
|
|
159
213
|
|