@account-kit/signer 4.31.2 → 4.33.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 +11 -8
- package/dist/esm/base.js +141 -38
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +132 -9
- package/dist/esm/client/base.js +34 -4
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +36 -14
- package/dist/esm/client/index.js +36 -18
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +19 -0
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/signer.d.ts +88 -33
- package/dist/esm/signer.js +28 -3
- package/dist/esm/signer.js.map +1 -1
- package/dist/esm/solanaSigner.d.ts +3 -3
- package/dist/esm/solanaSigner.js +1 -1
- package/dist/esm/solanaSigner.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/types.js.map +1 -1
- 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 +11 -8
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +132 -9
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +36 -14
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +19 -0
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/signer.d.ts +88 -33
- package/dist/types/signer.d.ts.map +1 -1
- package/dist/types/solanaSigner.d.ts +3 -3
- package/dist/types/solanaSigner.d.ts.map +1 -1
- package/dist/types/types.d.ts +1 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -7
- package/src/base.ts +191 -63
- package/src/client/base.ts +36 -7
- package/src/client/index.ts +41 -18
- package/src/client/types.ts +21 -0
- package/src/signer.ts +36 -3
- package/src/solanaSigner.ts +4 -4
- package/src/types.ts +1 -0
- package/src/version.ts +1 -1
package/src/base.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
takeBytes,
|
|
3
|
+
type SmartAccountAuthenticator,
|
|
4
|
+
type AuthorizationRequest,
|
|
5
|
+
} from "@aa-sdk/core";
|
|
2
6
|
import {
|
|
3
7
|
hashMessage,
|
|
4
8
|
hashTypedData,
|
|
@@ -10,28 +14,29 @@ import {
|
|
|
10
14
|
type LocalAccount,
|
|
11
15
|
type SerializeTransactionFn,
|
|
12
16
|
type SignableMessage,
|
|
17
|
+
type SignedAuthorization,
|
|
13
18
|
type TransactionSerializable,
|
|
14
19
|
type TransactionSerialized,
|
|
15
20
|
type TypedData,
|
|
16
21
|
type TypedDataDefinition,
|
|
17
22
|
} from "viem";
|
|
18
23
|
import { toAccount } from "viem/accounts";
|
|
19
|
-
import { hashAuthorization, type Authorization } from "viem/experimental";
|
|
20
24
|
import type { Mutate, StoreApi } from "zustand";
|
|
21
25
|
import { subscribeWithSelector } from "zustand/middleware";
|
|
22
26
|
import { createStore } from "zustand/vanilla";
|
|
23
27
|
import type { BaseSignerClient } from "./client/base";
|
|
24
|
-
import
|
|
25
|
-
EmailType,
|
|
26
|
-
MfaFactor,
|
|
27
|
-
OauthConfig,
|
|
28
|
-
OauthParams,
|
|
29
|
-
User,
|
|
30
|
-
VerifyMfaParams,
|
|
31
|
-
AddMfaParams,
|
|
32
|
-
AddMfaResult,
|
|
33
|
-
RemoveMfaParams,
|
|
34
|
-
|
|
28
|
+
import {
|
|
29
|
+
type EmailType,
|
|
30
|
+
type MfaFactor,
|
|
31
|
+
type OauthConfig,
|
|
32
|
+
type OauthParams,
|
|
33
|
+
type User,
|
|
34
|
+
type VerifyMfaParams,
|
|
35
|
+
type AddMfaParams,
|
|
36
|
+
type AddMfaResult,
|
|
37
|
+
type RemoveMfaParams,
|
|
38
|
+
type AuthLinkingPrompt,
|
|
39
|
+
} from "./client/types.js";
|
|
35
40
|
import { NotAuthenticatedError } from "./errors.js";
|
|
36
41
|
import { SignerLogger } from "./metrics.js";
|
|
37
42
|
import {
|
|
@@ -49,11 +54,13 @@ import {
|
|
|
49
54
|
type ValidateMultiFactorsArgs,
|
|
50
55
|
} from "./types.js";
|
|
51
56
|
import { assertNever } from "./utils/typeAssertions.js";
|
|
57
|
+
import { hashAuthorization } from "viem/utils";
|
|
52
58
|
|
|
53
59
|
export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
|
|
54
60
|
client: TClient;
|
|
55
61
|
sessionConfig?: Omit<SessionManagerParams, "client">;
|
|
56
62
|
initialError?: ErrorInfo;
|
|
63
|
+
initialAuthLinkingPrompt?: AuthLinkingPrompt;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
type AlchemySignerStore = {
|
|
@@ -67,6 +74,11 @@ type AlchemySignerStore = {
|
|
|
67
74
|
mfaFactorId?: string;
|
|
68
75
|
encryptedPayload?: string;
|
|
69
76
|
};
|
|
77
|
+
authLinkingStatus?: {
|
|
78
|
+
email: string;
|
|
79
|
+
providerName: string;
|
|
80
|
+
idToken: string;
|
|
81
|
+
};
|
|
70
82
|
};
|
|
71
83
|
|
|
72
84
|
type UnpackedSignature = {
|
|
@@ -115,6 +127,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
115
127
|
client,
|
|
116
128
|
sessionConfig,
|
|
117
129
|
initialError,
|
|
130
|
+
initialAuthLinkingPrompt,
|
|
118
131
|
}: BaseAlchemySignerParams<TClient>) {
|
|
119
132
|
this.inner = client;
|
|
120
133
|
this.store = createStore(
|
|
@@ -143,6 +156,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
143
156
|
// then initialize so that we can catch those events
|
|
144
157
|
this.sessionManager.initialize();
|
|
145
158
|
this.config = this.fetchConfig();
|
|
159
|
+
if (initialAuthLinkingPrompt) {
|
|
160
|
+
this.setAuthLinkingPrompt(initialAuthLinkingPrompt);
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
/**
|
|
@@ -161,52 +177,64 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
161
177
|
// is fired. In the Client and SessionManager we use EventEmitter because it's easier to handle internally
|
|
162
178
|
switch (event) {
|
|
163
179
|
case "connected":
|
|
164
|
-
return
|
|
180
|
+
return subscribeWithDelayedFireImmediately(
|
|
181
|
+
this.store,
|
|
165
182
|
({ status }) => status,
|
|
166
183
|
(status) =>
|
|
167
184
|
status === AlchemySignerStatus.CONNECTED &&
|
|
168
185
|
(listener as AlchemySignerEvents["connected"])(
|
|
169
186
|
this.store.getState().user!
|
|
170
|
-
)
|
|
171
|
-
{ fireImmediately: true }
|
|
187
|
+
)
|
|
172
188
|
);
|
|
173
189
|
case "disconnected":
|
|
174
|
-
return
|
|
190
|
+
return subscribeWithDelayedFireImmediately(
|
|
191
|
+
this.store,
|
|
175
192
|
({ status }) => status,
|
|
176
193
|
(status) =>
|
|
177
194
|
status === AlchemySignerStatus.DISCONNECTED &&
|
|
178
|
-
(listener as AlchemySignerEvents["disconnected"])()
|
|
179
|
-
{ fireImmediately: true }
|
|
195
|
+
(listener as AlchemySignerEvents["disconnected"])()
|
|
180
196
|
);
|
|
181
197
|
case "statusChanged":
|
|
182
|
-
return
|
|
198
|
+
return subscribeWithDelayedFireImmediately(
|
|
199
|
+
this.store,
|
|
183
200
|
({ status }) => status,
|
|
184
|
-
listener as AlchemySignerEvents["statusChanged"]
|
|
185
|
-
{ fireImmediately: true }
|
|
201
|
+
listener as AlchemySignerEvents["statusChanged"]
|
|
186
202
|
);
|
|
187
203
|
case "errorChanged":
|
|
188
|
-
return
|
|
204
|
+
return subscribeWithDelayedFireImmediately(
|
|
205
|
+
this.store,
|
|
189
206
|
({ error }) => error,
|
|
190
207
|
(error) =>
|
|
191
208
|
(listener as AlchemySignerEvents["errorChanged"])(
|
|
192
209
|
error ?? undefined
|
|
193
|
-
)
|
|
194
|
-
{ fireImmediately: true }
|
|
210
|
+
)
|
|
195
211
|
);
|
|
196
212
|
case "newUserSignup":
|
|
197
|
-
return
|
|
213
|
+
return subscribeWithDelayedFireImmediately(
|
|
214
|
+
this.store,
|
|
198
215
|
({ isNewUser }) => isNewUser,
|
|
199
216
|
(isNewUser) => {
|
|
200
217
|
if (isNewUser) (listener as AlchemySignerEvents["newUserSignup"])();
|
|
201
|
-
}
|
|
202
|
-
{ fireImmediately: true }
|
|
218
|
+
}
|
|
203
219
|
);
|
|
204
220
|
case "mfaStatusChanged":
|
|
205
|
-
return
|
|
221
|
+
return subscribeWithDelayedFireImmediately(
|
|
222
|
+
this.store,
|
|
206
223
|
({ mfaStatus }) => mfaStatus,
|
|
207
224
|
(mfaStatus) =>
|
|
208
|
-
(listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus)
|
|
209
|
-
|
|
225
|
+
(listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus)
|
|
226
|
+
);
|
|
227
|
+
case "emailAuthLinkingRequired":
|
|
228
|
+
return subscribeWithDelayedFireImmediately(
|
|
229
|
+
this.store,
|
|
230
|
+
({ authLinkingStatus }) => authLinkingStatus,
|
|
231
|
+
(authLinkingStatus) => {
|
|
232
|
+
if (authLinkingStatus) {
|
|
233
|
+
(listener as AlchemySignerEvents["emailAuthLinkingRequired"])(
|
|
234
|
+
authLinkingStatus.email
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
210
238
|
);
|
|
211
239
|
default:
|
|
212
240
|
assertNever(event, `Unknown event type ${event}`);
|
|
@@ -600,12 +628,12 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
600
628
|
* });
|
|
601
629
|
* ```
|
|
602
630
|
*
|
|
603
|
-
* @param {
|
|
604
|
-
* @returns {Promise<
|
|
631
|
+
* @param {AuthorizationRequest<number>} unsignedAuthorization the authorization to be signed
|
|
632
|
+
* @returns {Promise<SignedAuthorization<number>> | undefined} a promise that resolves to the authorization with the signature
|
|
605
633
|
*/
|
|
606
634
|
signAuthorization: (
|
|
607
|
-
unsignedAuthorization:
|
|
608
|
-
) => Promise<
|
|
635
|
+
unsignedAuthorization: AuthorizationRequest<number>
|
|
636
|
+
) => Promise<SignedAuthorization<number>> = SignerLogger.profiled(
|
|
609
637
|
"BaseAlchemySigner.signAuthorization",
|
|
610
638
|
async (unsignedAuthorization) => {
|
|
611
639
|
const hashedAuthorization = hashAuthorization(unsignedAuthorization);
|
|
@@ -613,7 +641,14 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
613
641
|
hashedAuthorization
|
|
614
642
|
);
|
|
615
643
|
const signature = this.unpackSignRawMessageBytes(signedAuthorizationHex);
|
|
616
|
-
|
|
644
|
+
const { address, contractAddress, ...unsignedAuthorizationRest } =
|
|
645
|
+
unsignedAuthorization;
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
...unsignedAuthorizationRest,
|
|
649
|
+
...signature,
|
|
650
|
+
address: address ?? contractAddress,
|
|
651
|
+
};
|
|
617
652
|
}
|
|
618
653
|
);
|
|
619
654
|
|
|
@@ -855,25 +890,19 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
855
890
|
params.redirectParams
|
|
856
891
|
);
|
|
857
892
|
|
|
858
|
-
this.
|
|
859
|
-
orgId,
|
|
860
|
-
isNewUser,
|
|
861
|
-
});
|
|
893
|
+
this.setAwaitingEmailAuth({ orgId, otpId, isNewUser });
|
|
862
894
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
895
|
+
// Clear the auth linking status if the email has changed. This would mean
|
|
896
|
+
// that the previously initiated social login is not associated with the
|
|
897
|
+
// email which is now being used to login.
|
|
898
|
+
const { authLinkingStatus } = this.store.getState();
|
|
899
|
+
if (authLinkingStatus && authLinkingStatus.email !== params.email) {
|
|
900
|
+
this.store.setState({ authLinkingStatus: undefined });
|
|
901
|
+
}
|
|
868
902
|
|
|
869
903
|
// We wait for the session manager to emit a connected event if
|
|
870
904
|
// cross tab sessions are permitted
|
|
871
|
-
return
|
|
872
|
-
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
873
|
-
resolve(session.user);
|
|
874
|
-
removeListener();
|
|
875
|
-
});
|
|
876
|
-
});
|
|
905
|
+
return this.waitForConnected();
|
|
877
906
|
};
|
|
878
907
|
|
|
879
908
|
private authenticateWithPasskey = async (
|
|
@@ -919,15 +948,20 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
919
948
|
private authenticateWithOauth = async (
|
|
920
949
|
args: Extract<AuthParams, { type: "oauth" }>
|
|
921
950
|
): Promise<User> => {
|
|
951
|
+
this.store.setState({ authLinkingStatus: undefined });
|
|
922
952
|
const params: OauthParams = {
|
|
923
953
|
...args,
|
|
924
954
|
expirationSeconds: this.getExpirationSeconds(),
|
|
925
955
|
};
|
|
926
956
|
if (params.mode === "redirect") {
|
|
927
957
|
return this.inner.oauthWithRedirect(params);
|
|
928
|
-
} else {
|
|
929
|
-
return this.inner.oauthWithPopup(params);
|
|
930
958
|
}
|
|
959
|
+
const result = await this.inner.oauthWithPopup(params);
|
|
960
|
+
if (!isAuthLinkingPrompt(result)) {
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
this.setAuthLinkingPrompt(result);
|
|
964
|
+
return this.waitForConnected();
|
|
931
965
|
};
|
|
932
966
|
|
|
933
967
|
private authenticateWithOtp = async (
|
|
@@ -953,16 +987,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
953
987
|
|
|
954
988
|
if (response.mfaRequired) {
|
|
955
989
|
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
|
-
});
|
|
990
|
+
return this.waitForConnected();
|
|
966
991
|
}
|
|
967
992
|
|
|
968
993
|
const user = await this.inner.completeAuthWithBundle({
|
|
@@ -980,9 +1005,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
980
1005
|
});
|
|
981
1006
|
}
|
|
982
1007
|
|
|
1008
|
+
const { authLinkingStatus } = this.store.getState();
|
|
1009
|
+
if (authLinkingStatus) {
|
|
1010
|
+
(async () => {
|
|
1011
|
+
this.inner.addOauthProvider({
|
|
1012
|
+
providerName: authLinkingStatus.providerName,
|
|
1013
|
+
oidcToken: authLinkingStatus.idToken,
|
|
1014
|
+
});
|
|
1015
|
+
})();
|
|
1016
|
+
}
|
|
1017
|
+
|
|
983
1018
|
return user;
|
|
984
1019
|
};
|
|
985
1020
|
|
|
1021
|
+
private setAwaitingEmailAuth = ({
|
|
1022
|
+
orgId,
|
|
1023
|
+
otpId,
|
|
1024
|
+
isNewUser,
|
|
1025
|
+
}: {
|
|
1026
|
+
orgId: string;
|
|
1027
|
+
otpId?: string;
|
|
1028
|
+
isNewUser?: boolean;
|
|
1029
|
+
}): void => {
|
|
1030
|
+
this.sessionManager.setTemporarySession({
|
|
1031
|
+
orgId,
|
|
1032
|
+
isNewUser,
|
|
1033
|
+
});
|
|
1034
|
+
this.store.setState({
|
|
1035
|
+
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
1036
|
+
otpId,
|
|
1037
|
+
error: null,
|
|
1038
|
+
});
|
|
1039
|
+
};
|
|
1040
|
+
|
|
986
1041
|
private handleOauthReturn = ({
|
|
987
1042
|
bundle,
|
|
988
1043
|
orgId,
|
|
@@ -1407,6 +1462,30 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
1407
1462
|
protected fetchConfig = async (): Promise<SignerConfig> => {
|
|
1408
1463
|
return this.inner.request("/v1/signer-config", {});
|
|
1409
1464
|
};
|
|
1465
|
+
|
|
1466
|
+
private setAuthLinkingPrompt = (prompt: AuthLinkingPrompt) => {
|
|
1467
|
+
this.setAwaitingEmailAuth({
|
|
1468
|
+
orgId: prompt.orgId,
|
|
1469
|
+
otpId: prompt.otpId,
|
|
1470
|
+
isNewUser: false,
|
|
1471
|
+
});
|
|
1472
|
+
this.store.setState({
|
|
1473
|
+
authLinkingStatus: {
|
|
1474
|
+
email: prompt.email,
|
|
1475
|
+
providerName: prompt.providerName,
|
|
1476
|
+
idToken: prompt.idToken,
|
|
1477
|
+
},
|
|
1478
|
+
});
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
private waitForConnected = (): Promise<User> => {
|
|
1482
|
+
return new Promise<User>((resolve) => {
|
|
1483
|
+
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
1484
|
+
resolve(session.user);
|
|
1485
|
+
removeListener();
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
};
|
|
1410
1489
|
}
|
|
1411
1490
|
|
|
1412
1491
|
function toErrorInfo(error: unknown): ErrorInfo {
|
|
@@ -1414,3 +1493,52 @@ function toErrorInfo(error: unknown): ErrorInfo {
|
|
|
1414
1493
|
? { name: error.name, message: error.message }
|
|
1415
1494
|
: { name: "Error", message: "Unknown error" };
|
|
1416
1495
|
}
|
|
1496
|
+
|
|
1497
|
+
// eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
|
|
1498
|
+
/**
|
|
1499
|
+
* Zustand's `fireImmediately` option calls the listener before
|
|
1500
|
+
* `store.subscribe` has returned, which breaks listeners which call
|
|
1501
|
+
* unsubscribe, e.g.
|
|
1502
|
+
*
|
|
1503
|
+
* ```ts
|
|
1504
|
+
* const unsubscribe = store.subscribe(
|
|
1505
|
+
* selector,
|
|
1506
|
+
* (update) => {
|
|
1507
|
+
* handleUpdate(update);
|
|
1508
|
+
* unsubscribe();
|
|
1509
|
+
* },
|
|
1510
|
+
* { fireImmediately: true },
|
|
1511
|
+
* )
|
|
1512
|
+
* ```
|
|
1513
|
+
*
|
|
1514
|
+
* since `unsubscribe` is still undefined at the time the listener is called. To
|
|
1515
|
+
* prevent this, if the listener triggers before `subscribe` has returned, delay
|
|
1516
|
+
* the callback to a later run of the event loop.
|
|
1517
|
+
*/
|
|
1518
|
+
function subscribeWithDelayedFireImmediately<T>(
|
|
1519
|
+
store: InternalStore,
|
|
1520
|
+
selector: (state: AlchemySignerStore) => T,
|
|
1521
|
+
listener: (selectedState: T, previousSelectedState: T) => void
|
|
1522
|
+
): () => void {
|
|
1523
|
+
let subscribeHasReturned = false;
|
|
1524
|
+
const unsubscribe = store.subscribe(
|
|
1525
|
+
selector,
|
|
1526
|
+
(...args) => {
|
|
1527
|
+
if (subscribeHasReturned) {
|
|
1528
|
+
listener(...args);
|
|
1529
|
+
} else {
|
|
1530
|
+
setTimeout(() => listener(...args), 0);
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
{ fireImmediately: true }
|
|
1534
|
+
);
|
|
1535
|
+
subscribeHasReturned = true;
|
|
1536
|
+
return unsubscribe;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function isAuthLinkingPrompt(result: unknown): result is AuthLinkingPrompt {
|
|
1540
|
+
return (
|
|
1541
|
+
(result as AuthLinkingPrompt)?.status ===
|
|
1542
|
+
"ACCOUNT_LINKING_CONFIRMATION_REQUIRED"
|
|
1543
|
+
);
|
|
1544
|
+
}
|
package/src/client/base.ts
CHANGED
|
@@ -34,6 +34,8 @@ import type {
|
|
|
34
34
|
VerifyMfaParams,
|
|
35
35
|
SubmitOtpCodeResponse,
|
|
36
36
|
ValidateMultiFactorsParams,
|
|
37
|
+
AuthLinkingPrompt,
|
|
38
|
+
AddOauthProviderParams,
|
|
37
39
|
} from "./types.js";
|
|
38
40
|
import { VERSION } from "../version.js";
|
|
39
41
|
|
|
@@ -99,13 +101,13 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
protected set user(user: User | undefined) {
|
|
102
|
-
|
|
104
|
+
const previousUser = this._user;
|
|
105
|
+
this._user = user;
|
|
106
|
+
if (user && !previousUser) {
|
|
103
107
|
this.eventEmitter.emit("connected", user);
|
|
104
|
-
} else if (!user &&
|
|
108
|
+
} else if (!user && previousUser) {
|
|
105
109
|
this.eventEmitter.emit("disconnected");
|
|
106
110
|
}
|
|
107
|
-
|
|
108
|
-
this._user = user;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/**
|
|
@@ -160,11 +162,11 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
160
162
|
|
|
161
163
|
public abstract oauthWithRedirect(
|
|
162
164
|
args: Extract<OauthParams, { mode: "redirect" }>
|
|
163
|
-
): Promise<User
|
|
165
|
+
): Promise<User>;
|
|
164
166
|
|
|
165
167
|
public abstract oauthWithPopup(
|
|
166
168
|
args: Extract<OauthParams, { mode: "popup" }>
|
|
167
|
-
): Promise<User>;
|
|
169
|
+
): Promise<User | AuthLinkingPrompt>;
|
|
168
170
|
|
|
169
171
|
public abstract submitOtpCode(
|
|
170
172
|
args: Omit<OtpParams, "targetPublicKey">
|
|
@@ -266,6 +268,32 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
266
268
|
};
|
|
267
269
|
};
|
|
268
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Adds an OAuth provider for the authenticated user using the provided parameters. Throws an error if the user is not authenticated.
|
|
273
|
+
*
|
|
274
|
+
* @param {AddOauthProviderParams} params The parameters for adding an OAuth provider, including `providerName` and `oidcToken`.
|
|
275
|
+
* @throws {NotAuthenticatedError} Throws if the user is not authenticated.
|
|
276
|
+
* @returns {Promise<void>} A Promise that resolves when the OAuth provider is added.
|
|
277
|
+
*/
|
|
278
|
+
public addOauthProvider = async (
|
|
279
|
+
params: AddOauthProviderParams
|
|
280
|
+
): Promise<void> => {
|
|
281
|
+
if (!this.user) {
|
|
282
|
+
throw new NotAuthenticatedError();
|
|
283
|
+
}
|
|
284
|
+
const { providerName, oidcToken } = params;
|
|
285
|
+
const stampedRequest = await this.turnkeyClient.stampCreateOauthProviders({
|
|
286
|
+
type: "ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS",
|
|
287
|
+
timestampMs: Date.now().toString(),
|
|
288
|
+
organizationId: this.user.orgId,
|
|
289
|
+
parameters: {
|
|
290
|
+
userId: this.user.userId,
|
|
291
|
+
oauthProviders: [{ providerName, oidcToken }],
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
await this.request("/v1/add-oauth-provider", { stampedRequest });
|
|
295
|
+
};
|
|
296
|
+
|
|
269
297
|
/**
|
|
270
298
|
* Retrieves the current user or fetches the user information if not already available.
|
|
271
299
|
*
|
|
@@ -374,7 +402,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
374
402
|
throw new Error("User must be authenticated to create api key");
|
|
375
403
|
}
|
|
376
404
|
const resp = await this.turnkeyClient.createApiKeys({
|
|
377
|
-
type: "
|
|
405
|
+
type: "ACTIVITY_TYPE_CREATE_API_KEYS_V2",
|
|
378
406
|
timestampMs: new Date().getTime().toString(),
|
|
379
407
|
organizationId: this.user.orgId,
|
|
380
408
|
parameters: {
|
|
@@ -382,6 +410,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
382
410
|
{
|
|
383
411
|
apiKeyName: params.name,
|
|
384
412
|
publicKey: params.publicKey,
|
|
413
|
+
curveType: "API_KEY_CURVE_P256",
|
|
385
414
|
expirationSeconds: params.expirationSec.toString(),
|
|
386
415
|
},
|
|
387
416
|
],
|
package/src/client/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
OtpParams,
|
|
19
19
|
User,
|
|
20
20
|
SubmitOtpCodeResponse,
|
|
21
|
+
AuthLinkingPrompt,
|
|
21
22
|
} from "./types.js";
|
|
22
23
|
import { MfaRequiredError } from "../errors.js";
|
|
23
24
|
import { parseMfaError } from "../utils/parseMfaError.js";
|
|
@@ -531,7 +532,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
|
|
|
531
532
|
*/
|
|
532
533
|
public override oauthWithPopup = async (
|
|
533
534
|
args: Extract<AuthParams, { type: "oauth"; mode: "popup" }>
|
|
534
|
-
): Promise<User> => {
|
|
535
|
+
): Promise<User | AuthLinkingPrompt> => {
|
|
535
536
|
const turnkeyPublicKey = await this.initIframeStamper();
|
|
536
537
|
const oauthParams = args;
|
|
537
538
|
const providerUrl = await this.getOauthProviderUrl({
|
|
@@ -551,33 +552,55 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
|
|
|
551
552
|
return;
|
|
552
553
|
}
|
|
553
554
|
const {
|
|
555
|
+
alchemyStatus: status,
|
|
554
556
|
alchemyBundle: bundle,
|
|
555
557
|
alchemyOrgId: orgId,
|
|
556
558
|
alchemyIdToken: idToken,
|
|
557
559
|
alchemyIsSignup: isSignup,
|
|
558
560
|
alchemyError,
|
|
561
|
+
alchemyOtpId: otpId,
|
|
562
|
+
alchemyEmail: email,
|
|
563
|
+
alchemyAuthProvider: providerName,
|
|
559
564
|
} = event.data;
|
|
560
|
-
if (
|
|
561
|
-
cleanup();
|
|
562
|
-
popup?.close();
|
|
563
|
-
this.completeAuthWithBundle({
|
|
564
|
-
bundle,
|
|
565
|
-
orgId,
|
|
566
|
-
connectedEventName: "connectedOauth",
|
|
567
|
-
idToken,
|
|
568
|
-
authenticatingType: "oauth",
|
|
569
|
-
}).then((user) => {
|
|
570
|
-
if (isSignup) {
|
|
571
|
-
eventEmitter.emit("newUserSignup");
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
resolve(user);
|
|
575
|
-
}, reject);
|
|
576
|
-
} else if (alchemyError) {
|
|
565
|
+
if (alchemyError) {
|
|
577
566
|
cleanup();
|
|
578
567
|
popup?.close();
|
|
579
568
|
reject(new OauthFailedError(alchemyError));
|
|
580
569
|
}
|
|
570
|
+
if (!status) {
|
|
571
|
+
// This message isn't meant for us.
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
cleanup();
|
|
575
|
+
popup?.close();
|
|
576
|
+
switch (status) {
|
|
577
|
+
case "SUCCESS":
|
|
578
|
+
this.completeAuthWithBundle({
|
|
579
|
+
bundle,
|
|
580
|
+
orgId,
|
|
581
|
+
connectedEventName: "connectedOauth",
|
|
582
|
+
idToken,
|
|
583
|
+
authenticatingType: "oauth",
|
|
584
|
+
}).then((user) => {
|
|
585
|
+
if (isSignup) {
|
|
586
|
+
eventEmitter.emit("newUserSignup");
|
|
587
|
+
}
|
|
588
|
+
resolve(user);
|
|
589
|
+
}, reject);
|
|
590
|
+
break;
|
|
591
|
+
case "ACCOUNT_LINKING_CONFIRMATION_REQUIRED":
|
|
592
|
+
resolve({
|
|
593
|
+
status,
|
|
594
|
+
idToken,
|
|
595
|
+
email,
|
|
596
|
+
providerName,
|
|
597
|
+
otpId,
|
|
598
|
+
orgId,
|
|
599
|
+
} satisfies AuthLinkingPrompt);
|
|
600
|
+
break;
|
|
601
|
+
default:
|
|
602
|
+
reject(new Error(`Unknown status: ${status}`));
|
|
603
|
+
}
|
|
581
604
|
};
|
|
582
605
|
|
|
583
606
|
window.addEventListener("message", handleMessage);
|
package/src/client/types.ts
CHANGED
|
@@ -171,6 +171,13 @@ export type SignerEndpoints = [
|
|
|
171
171
|
signature: Hex;
|
|
172
172
|
};
|
|
173
173
|
},
|
|
174
|
+
{
|
|
175
|
+
Route: "/v1/add-oauth-provider";
|
|
176
|
+
Body: {
|
|
177
|
+
stampedRequest: TSignedRequest;
|
|
178
|
+
};
|
|
179
|
+
Response: void;
|
|
180
|
+
},
|
|
174
181
|
{
|
|
175
182
|
Route: "/v1/prepare-oauth";
|
|
176
183
|
Body: {
|
|
@@ -262,6 +269,15 @@ export type GetWebAuthnAttestationResult = {
|
|
|
262
269
|
authenticatorUserId: ArrayBuffer;
|
|
263
270
|
};
|
|
264
271
|
|
|
272
|
+
export type AuthLinkingPrompt = {
|
|
273
|
+
status: "ACCOUNT_LINKING_CONFIRMATION_REQUIRED";
|
|
274
|
+
idToken: string;
|
|
275
|
+
email: string;
|
|
276
|
+
providerName: string;
|
|
277
|
+
otpId: string;
|
|
278
|
+
orgId: string;
|
|
279
|
+
};
|
|
280
|
+
|
|
265
281
|
export type OauthState = {
|
|
266
282
|
authProviderId: string;
|
|
267
283
|
isCustomProvider?: boolean;
|
|
@@ -324,6 +340,11 @@ export type SubmitOtpCodeResponse =
|
|
|
324
340
|
| { bundle: string; mfaRequired: false }
|
|
325
341
|
| { mfaRequired: true; encryptedPayload: string; multiFactors: MfaFactor[] };
|
|
326
342
|
|
|
343
|
+
export type AddOauthProviderParams = {
|
|
344
|
+
providerName: string;
|
|
345
|
+
oidcToken: string;
|
|
346
|
+
};
|
|
347
|
+
|
|
327
348
|
export type experimental_CreateApiKeyParams = {
|
|
328
349
|
name: string;
|
|
329
350
|
publicKey: string;
|
package/src/signer.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AlchemySignerWebClient,
|
|
6
6
|
} from "./client/index.js";
|
|
7
7
|
import type {
|
|
8
|
+
AuthLinkingPrompt,
|
|
8
9
|
CredentialCreationOptionOverrides,
|
|
9
10
|
VerifyMfaParams,
|
|
10
11
|
} from "./client/types.js";
|
|
@@ -141,20 +142,28 @@ export class AlchemyWebSigner extends BaseAlchemySigner<AlchemySignerWebClient>
|
|
|
141
142
|
emailBundle: "bundle",
|
|
142
143
|
// We don't need this, but we still want to remove it from the URL.
|
|
143
144
|
emailOrgId: "orgId",
|
|
145
|
+
status: "alchemy-status",
|
|
144
146
|
oauthBundle: "alchemy-bundle",
|
|
145
147
|
oauthOrgId: "alchemy-org-id",
|
|
146
|
-
oauthError: "alchemy-error",
|
|
147
148
|
idToken: "alchemy-id-token",
|
|
148
149
|
isSignup: "aa-is-signup",
|
|
150
|
+
otpId: "alchemy-otp-id",
|
|
151
|
+
email: "alchemy-email",
|
|
152
|
+
authProvider: "alchemy-auth-provider",
|
|
153
|
+
oauthError: "alchemy-error",
|
|
149
154
|
};
|
|
150
155
|
|
|
151
156
|
const {
|
|
152
157
|
emailBundle,
|
|
158
|
+
status,
|
|
153
159
|
oauthBundle,
|
|
154
160
|
oauthOrgId,
|
|
155
|
-
oauthError,
|
|
156
161
|
idToken,
|
|
157
162
|
isSignup,
|
|
163
|
+
otpId,
|
|
164
|
+
email,
|
|
165
|
+
authProvider,
|
|
166
|
+
oauthError,
|
|
158
167
|
} = getAndRemoveQueryParams(qpStructure);
|
|
159
168
|
|
|
160
169
|
if (!AlchemyWebSigner.replaceStateFilterInstalled) {
|
|
@@ -167,7 +176,31 @@ export class AlchemyWebSigner extends BaseAlchemySigner<AlchemySignerWebClient>
|
|
|
167
176
|
? { name: "OauthError", message: oauthError }
|
|
168
177
|
: undefined;
|
|
169
178
|
|
|
170
|
-
|
|
179
|
+
const initialAuthLinkingPrompt: AuthLinkingPrompt | undefined = (() => {
|
|
180
|
+
if (status !== "ACCOUNT_LINKING_CONFIRMATION_REQUIRED") {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
idToken == null ||
|
|
185
|
+
email == null ||
|
|
186
|
+
authProvider == null ||
|
|
187
|
+
otpId == null ||
|
|
188
|
+
oauthOrgId == null
|
|
189
|
+
) {
|
|
190
|
+
console.error("Missing required query params for auth linking prompt");
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
status,
|
|
195
|
+
idToken,
|
|
196
|
+
email,
|
|
197
|
+
providerName: authProvider,
|
|
198
|
+
otpId,
|
|
199
|
+
orgId: oauthOrgId,
|
|
200
|
+
};
|
|
201
|
+
})();
|
|
202
|
+
|
|
203
|
+
super({ client, sessionConfig, initialError, initialAuthLinkingPrompt });
|
|
171
204
|
|
|
172
205
|
const isNewUser = isSignup === "true";
|
|
173
206
|
|