@account-kit/signer 4.31.2 → 4.32.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 +6 -2
- package/dist/esm/base.js +131 -33
- 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 +6 -2
- 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 +5 -6
- package/src/base.ts +172 -56
- 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
|
@@ -21,17 +21,18 @@ 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
|
|
25
|
-
EmailType,
|
|
26
|
-
MfaFactor,
|
|
27
|
-
OauthConfig,
|
|
28
|
-
OauthParams,
|
|
29
|
-
User,
|
|
30
|
-
VerifyMfaParams,
|
|
31
|
-
AddMfaParams,
|
|
32
|
-
AddMfaResult,
|
|
33
|
-
RemoveMfaParams,
|
|
34
|
-
|
|
24
|
+
import {
|
|
25
|
+
type EmailType,
|
|
26
|
+
type MfaFactor,
|
|
27
|
+
type OauthConfig,
|
|
28
|
+
type OauthParams,
|
|
29
|
+
type User,
|
|
30
|
+
type VerifyMfaParams,
|
|
31
|
+
type AddMfaParams,
|
|
32
|
+
type AddMfaResult,
|
|
33
|
+
type RemoveMfaParams,
|
|
34
|
+
type AuthLinkingPrompt,
|
|
35
|
+
} from "./client/types.js";
|
|
35
36
|
import { NotAuthenticatedError } from "./errors.js";
|
|
36
37
|
import { SignerLogger } from "./metrics.js";
|
|
37
38
|
import {
|
|
@@ -54,6 +55,7 @@ export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
|
|
|
54
55
|
client: TClient;
|
|
55
56
|
sessionConfig?: Omit<SessionManagerParams, "client">;
|
|
56
57
|
initialError?: ErrorInfo;
|
|
58
|
+
initialAuthLinkingPrompt?: AuthLinkingPrompt;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
type AlchemySignerStore = {
|
|
@@ -67,6 +69,11 @@ type AlchemySignerStore = {
|
|
|
67
69
|
mfaFactorId?: string;
|
|
68
70
|
encryptedPayload?: string;
|
|
69
71
|
};
|
|
72
|
+
authLinkingStatus?: {
|
|
73
|
+
email: string;
|
|
74
|
+
providerName: string;
|
|
75
|
+
idToken: string;
|
|
76
|
+
};
|
|
70
77
|
};
|
|
71
78
|
|
|
72
79
|
type UnpackedSignature = {
|
|
@@ -115,6 +122,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
115
122
|
client,
|
|
116
123
|
sessionConfig,
|
|
117
124
|
initialError,
|
|
125
|
+
initialAuthLinkingPrompt,
|
|
118
126
|
}: BaseAlchemySignerParams<TClient>) {
|
|
119
127
|
this.inner = client;
|
|
120
128
|
this.store = createStore(
|
|
@@ -143,6 +151,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
143
151
|
// then initialize so that we can catch those events
|
|
144
152
|
this.sessionManager.initialize();
|
|
145
153
|
this.config = this.fetchConfig();
|
|
154
|
+
if (initialAuthLinkingPrompt) {
|
|
155
|
+
this.setAuthLinkingPrompt(initialAuthLinkingPrompt);
|
|
156
|
+
}
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
/**
|
|
@@ -161,52 +172,64 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
161
172
|
// is fired. In the Client and SessionManager we use EventEmitter because it's easier to handle internally
|
|
162
173
|
switch (event) {
|
|
163
174
|
case "connected":
|
|
164
|
-
return
|
|
175
|
+
return subscribeWithDelayedFireImmediately(
|
|
176
|
+
this.store,
|
|
165
177
|
({ status }) => status,
|
|
166
178
|
(status) =>
|
|
167
179
|
status === AlchemySignerStatus.CONNECTED &&
|
|
168
180
|
(listener as AlchemySignerEvents["connected"])(
|
|
169
181
|
this.store.getState().user!
|
|
170
|
-
)
|
|
171
|
-
{ fireImmediately: true }
|
|
182
|
+
)
|
|
172
183
|
);
|
|
173
184
|
case "disconnected":
|
|
174
|
-
return
|
|
185
|
+
return subscribeWithDelayedFireImmediately(
|
|
186
|
+
this.store,
|
|
175
187
|
({ status }) => status,
|
|
176
188
|
(status) =>
|
|
177
189
|
status === AlchemySignerStatus.DISCONNECTED &&
|
|
178
|
-
(listener as AlchemySignerEvents["disconnected"])()
|
|
179
|
-
{ fireImmediately: true }
|
|
190
|
+
(listener as AlchemySignerEvents["disconnected"])()
|
|
180
191
|
);
|
|
181
192
|
case "statusChanged":
|
|
182
|
-
return
|
|
193
|
+
return subscribeWithDelayedFireImmediately(
|
|
194
|
+
this.store,
|
|
183
195
|
({ status }) => status,
|
|
184
|
-
listener as AlchemySignerEvents["statusChanged"]
|
|
185
|
-
{ fireImmediately: true }
|
|
196
|
+
listener as AlchemySignerEvents["statusChanged"]
|
|
186
197
|
);
|
|
187
198
|
case "errorChanged":
|
|
188
|
-
return
|
|
199
|
+
return subscribeWithDelayedFireImmediately(
|
|
200
|
+
this.store,
|
|
189
201
|
({ error }) => error,
|
|
190
202
|
(error) =>
|
|
191
203
|
(listener as AlchemySignerEvents["errorChanged"])(
|
|
192
204
|
error ?? undefined
|
|
193
|
-
)
|
|
194
|
-
{ fireImmediately: true }
|
|
205
|
+
)
|
|
195
206
|
);
|
|
196
207
|
case "newUserSignup":
|
|
197
|
-
return
|
|
208
|
+
return subscribeWithDelayedFireImmediately(
|
|
209
|
+
this.store,
|
|
198
210
|
({ isNewUser }) => isNewUser,
|
|
199
211
|
(isNewUser) => {
|
|
200
212
|
if (isNewUser) (listener as AlchemySignerEvents["newUserSignup"])();
|
|
201
|
-
}
|
|
202
|
-
{ fireImmediately: true }
|
|
213
|
+
}
|
|
203
214
|
);
|
|
204
215
|
case "mfaStatusChanged":
|
|
205
|
-
return
|
|
216
|
+
return subscribeWithDelayedFireImmediately(
|
|
217
|
+
this.store,
|
|
206
218
|
({ mfaStatus }) => mfaStatus,
|
|
207
219
|
(mfaStatus) =>
|
|
208
|
-
(listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus)
|
|
209
|
-
|
|
220
|
+
(listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus)
|
|
221
|
+
);
|
|
222
|
+
case "emailAuthLinkingRequired":
|
|
223
|
+
return subscribeWithDelayedFireImmediately(
|
|
224
|
+
this.store,
|
|
225
|
+
({ authLinkingStatus }) => authLinkingStatus,
|
|
226
|
+
(authLinkingStatus) => {
|
|
227
|
+
if (authLinkingStatus) {
|
|
228
|
+
(listener as AlchemySignerEvents["emailAuthLinkingRequired"])(
|
|
229
|
+
authLinkingStatus.email
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
210
233
|
);
|
|
211
234
|
default:
|
|
212
235
|
assertNever(event, `Unknown event type ${event}`);
|
|
@@ -855,25 +878,19 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
855
878
|
params.redirectParams
|
|
856
879
|
);
|
|
857
880
|
|
|
858
|
-
this.
|
|
859
|
-
orgId,
|
|
860
|
-
isNewUser,
|
|
861
|
-
});
|
|
881
|
+
this.setAwaitingEmailAuth({ orgId, otpId, isNewUser });
|
|
862
882
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
883
|
+
// Clear the auth linking status if the email has changed. This would mean
|
|
884
|
+
// that the previously initiated social login is not associated with the
|
|
885
|
+
// email which is now being used to login.
|
|
886
|
+
const { authLinkingStatus } = this.store.getState();
|
|
887
|
+
if (authLinkingStatus && authLinkingStatus.email !== params.email) {
|
|
888
|
+
this.store.setState({ authLinkingStatus: undefined });
|
|
889
|
+
}
|
|
868
890
|
|
|
869
891
|
// We wait for the session manager to emit a connected event if
|
|
870
892
|
// cross tab sessions are permitted
|
|
871
|
-
return
|
|
872
|
-
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
873
|
-
resolve(session.user);
|
|
874
|
-
removeListener();
|
|
875
|
-
});
|
|
876
|
-
});
|
|
893
|
+
return this.waitForConnected();
|
|
877
894
|
};
|
|
878
895
|
|
|
879
896
|
private authenticateWithPasskey = async (
|
|
@@ -919,15 +936,20 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
919
936
|
private authenticateWithOauth = async (
|
|
920
937
|
args: Extract<AuthParams, { type: "oauth" }>
|
|
921
938
|
): Promise<User> => {
|
|
939
|
+
this.store.setState({ authLinkingStatus: undefined });
|
|
922
940
|
const params: OauthParams = {
|
|
923
941
|
...args,
|
|
924
942
|
expirationSeconds: this.getExpirationSeconds(),
|
|
925
943
|
};
|
|
926
944
|
if (params.mode === "redirect") {
|
|
927
945
|
return this.inner.oauthWithRedirect(params);
|
|
928
|
-
} else {
|
|
929
|
-
return this.inner.oauthWithPopup(params);
|
|
930
946
|
}
|
|
947
|
+
const result = await this.inner.oauthWithPopup(params);
|
|
948
|
+
if (!isAuthLinkingPrompt(result)) {
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
this.setAuthLinkingPrompt(result);
|
|
952
|
+
return this.waitForConnected();
|
|
931
953
|
};
|
|
932
954
|
|
|
933
955
|
private authenticateWithOtp = async (
|
|
@@ -953,16 +975,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
953
975
|
|
|
954
976
|
if (response.mfaRequired) {
|
|
955
977
|
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
|
-
});
|
|
978
|
+
return this.waitForConnected();
|
|
966
979
|
}
|
|
967
980
|
|
|
968
981
|
const user = await this.inner.completeAuthWithBundle({
|
|
@@ -980,9 +993,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
980
993
|
});
|
|
981
994
|
}
|
|
982
995
|
|
|
996
|
+
const { authLinkingStatus } = this.store.getState();
|
|
997
|
+
if (authLinkingStatus) {
|
|
998
|
+
(async () => {
|
|
999
|
+
this.inner.addOauthProvider({
|
|
1000
|
+
providerName: authLinkingStatus.providerName,
|
|
1001
|
+
oidcToken: authLinkingStatus.idToken,
|
|
1002
|
+
});
|
|
1003
|
+
})();
|
|
1004
|
+
}
|
|
1005
|
+
|
|
983
1006
|
return user;
|
|
984
1007
|
};
|
|
985
1008
|
|
|
1009
|
+
private setAwaitingEmailAuth = ({
|
|
1010
|
+
orgId,
|
|
1011
|
+
otpId,
|
|
1012
|
+
isNewUser,
|
|
1013
|
+
}: {
|
|
1014
|
+
orgId: string;
|
|
1015
|
+
otpId?: string;
|
|
1016
|
+
isNewUser?: boolean;
|
|
1017
|
+
}): void => {
|
|
1018
|
+
this.sessionManager.setTemporarySession({
|
|
1019
|
+
orgId,
|
|
1020
|
+
isNewUser,
|
|
1021
|
+
});
|
|
1022
|
+
this.store.setState({
|
|
1023
|
+
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
1024
|
+
otpId,
|
|
1025
|
+
error: null,
|
|
1026
|
+
});
|
|
1027
|
+
};
|
|
1028
|
+
|
|
986
1029
|
private handleOauthReturn = ({
|
|
987
1030
|
bundle,
|
|
988
1031
|
orgId,
|
|
@@ -1407,6 +1450,30 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
1407
1450
|
protected fetchConfig = async (): Promise<SignerConfig> => {
|
|
1408
1451
|
return this.inner.request("/v1/signer-config", {});
|
|
1409
1452
|
};
|
|
1453
|
+
|
|
1454
|
+
private setAuthLinkingPrompt = (prompt: AuthLinkingPrompt) => {
|
|
1455
|
+
this.setAwaitingEmailAuth({
|
|
1456
|
+
orgId: prompt.orgId,
|
|
1457
|
+
otpId: prompt.otpId,
|
|
1458
|
+
isNewUser: false,
|
|
1459
|
+
});
|
|
1460
|
+
this.store.setState({
|
|
1461
|
+
authLinkingStatus: {
|
|
1462
|
+
email: prompt.email,
|
|
1463
|
+
providerName: prompt.providerName,
|
|
1464
|
+
idToken: prompt.idToken,
|
|
1465
|
+
},
|
|
1466
|
+
});
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
private waitForConnected = (): Promise<User> => {
|
|
1470
|
+
return new Promise<User>((resolve) => {
|
|
1471
|
+
const removeListener = this.sessionManager.on("connected", (session) => {
|
|
1472
|
+
resolve(session.user);
|
|
1473
|
+
removeListener();
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
};
|
|
1410
1477
|
}
|
|
1411
1478
|
|
|
1412
1479
|
function toErrorInfo(error: unknown): ErrorInfo {
|
|
@@ -1414,3 +1481,52 @@ function toErrorInfo(error: unknown): ErrorInfo {
|
|
|
1414
1481
|
? { name: error.name, message: error.message }
|
|
1415
1482
|
: { name: "Error", message: "Unknown error" };
|
|
1416
1483
|
}
|
|
1484
|
+
|
|
1485
|
+
// eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
|
|
1486
|
+
/**
|
|
1487
|
+
* Zustand's `fireImmediately` option calls the listener before
|
|
1488
|
+
* `store.subscribe` has returned, which breaks listeners which call
|
|
1489
|
+
* unsubscribe, e.g.
|
|
1490
|
+
*
|
|
1491
|
+
* ```ts
|
|
1492
|
+
* const unsubscribe = store.subscribe(
|
|
1493
|
+
* selector,
|
|
1494
|
+
* (update) => {
|
|
1495
|
+
* handleUpdate(update);
|
|
1496
|
+
* unsubscribe();
|
|
1497
|
+
* },
|
|
1498
|
+
* { fireImmediately: true },
|
|
1499
|
+
* )
|
|
1500
|
+
* ```
|
|
1501
|
+
*
|
|
1502
|
+
* since `unsubscribe` is still undefined at the time the listener is called. To
|
|
1503
|
+
* prevent this, if the listener triggers before `subscribe` has returned, delay
|
|
1504
|
+
* the callback to a later run of the event loop.
|
|
1505
|
+
*/
|
|
1506
|
+
function subscribeWithDelayedFireImmediately<T>(
|
|
1507
|
+
store: InternalStore,
|
|
1508
|
+
selector: (state: AlchemySignerStore) => T,
|
|
1509
|
+
listener: (selectedState: T, previousSelectedState: T) => void
|
|
1510
|
+
): () => void {
|
|
1511
|
+
let subscribeHasReturned = false;
|
|
1512
|
+
const unsubscribe = store.subscribe(
|
|
1513
|
+
selector,
|
|
1514
|
+
(...args) => {
|
|
1515
|
+
if (subscribeHasReturned) {
|
|
1516
|
+
listener(...args);
|
|
1517
|
+
} else {
|
|
1518
|
+
setTimeout(() => listener(...args), 0);
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
{ fireImmediately: true }
|
|
1522
|
+
);
|
|
1523
|
+
subscribeHasReturned = true;
|
|
1524
|
+
return unsubscribe;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function isAuthLinkingPrompt(result: unknown): result is AuthLinkingPrompt {
|
|
1528
|
+
return (
|
|
1529
|
+
(result as AuthLinkingPrompt)?.status ===
|
|
1530
|
+
"ACCOUNT_LINKING_CONFIRMATION_REQUIRED"
|
|
1531
|
+
);
|
|
1532
|
+
}
|
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
|
|
package/src/solanaSigner.ts
CHANGED
|
@@ -88,17 +88,17 @@ export class SolanaSigner {
|
|
|
88
88
|
return toBytes(this.formatSignatureForSolana(signature));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
async
|
|
91
|
+
async createTransaction(
|
|
92
92
|
instructions: TransactionInstruction[],
|
|
93
93
|
connection: Connection,
|
|
94
94
|
version?: "versioned"
|
|
95
95
|
): Promise<VersionedTransaction>;
|
|
96
|
-
async
|
|
96
|
+
async createTransaction(
|
|
97
97
|
instructions: TransactionInstruction[],
|
|
98
98
|
connection: Connection,
|
|
99
99
|
version?: "legacy"
|
|
100
100
|
): Promise<Transaction>;
|
|
101
|
-
async
|
|
101
|
+
async createTransaction(
|
|
102
102
|
instructions: TransactionInstruction[],
|
|
103
103
|
connection: Connection
|
|
104
104
|
): Promise<VersionedTransaction>;
|
|
@@ -111,7 +111,7 @@ export class SolanaSigner {
|
|
|
111
111
|
* @param {"versioned" | "legacy"} [version] - The version of the transaction
|
|
112
112
|
* @returns {Promise<Transaction | VersionedTransaction>} The transfer transaction
|
|
113
113
|
*/
|
|
114
|
-
async
|
|
114
|
+
async createTransaction(
|
|
115
115
|
instructions: TransactionInstruction[],
|
|
116
116
|
connection: Connection,
|
|
117
117
|
version?: string
|
package/src/types.ts
CHANGED
package/src/version.ts
CHANGED