@turnkey/react-wallet-kit 1.7.1 → 1.8.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/components/import/Import.d.ts +2 -0
- package/dist/components/import/Import.d.ts.map +1 -1
- package/dist/components/import/Import.js +3 -2
- package/dist/components/import/Import.js.map +1 -1
- package/dist/components/import/Import.mjs +4 -3
- package/dist/components/import/Import.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/providers/client/Provider.d.ts +1 -1
- package/dist/providers/client/Provider.d.ts.map +1 -1
- package/dist/providers/client/Provider.js +684 -880
- package/dist/providers/client/Provider.js.map +1 -1
- package/dist/providers/client/Provider.mjs +2329 -2494
- package/dist/providers/client/Provider.mjs.map +1 -1
- package/dist/providers/client/Types.d.ts +4 -0
- package/dist/providers/client/Types.d.ts.map +1 -1
- package/dist/providers/client/Types.js.map +1 -1
- package/dist/providers/client/Types.mjs.map +1 -1
- package/dist/tests/oauth-test.d.ts +2 -0
- package/dist/tests/oauth-test.d.ts.map +1 -0
- package/dist/types/method-types.d.ts +2 -0
- package/dist/types/method-types.d.ts.map +1 -1
- package/dist/utils/oauth.d.ts +323 -0
- package/dist/utils/oauth.d.ts.map +1 -0
- package/dist/utils/oauth.js +613 -0
- package/dist/utils/oauth.js.map +1 -0
- package/dist/utils/oauth.mjs +670 -0
- package/dist/utils/oauth.mjs.map +1 -0
- package/dist/utils/utils.d.ts +1 -41
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +0 -161
- package/dist/utils/utils.js.map +1 -1
- package/dist/utils/utils.mjs +2 -216
- package/dist/utils/utils.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var sha2 = require('@noble/hashes/sha2');
|
|
6
6
|
var utils$1 = require('@noble/hashes/utils');
|
|
7
|
+
var oauth = require('../../utils/oauth.js');
|
|
7
8
|
var utils = require('../../utils/utils.js');
|
|
8
9
|
var timers = require('../../utils/timers.js');
|
|
9
10
|
var core = require('@turnkey/core');
|
|
@@ -13,7 +14,6 @@ var Hook = require('../modal/Hook.js');
|
|
|
13
14
|
var base = require('../../types/base.js');
|
|
14
15
|
var index = require('../../components/auth/index.js');
|
|
15
16
|
var reactFontawesome = require('@fortawesome/react-fontawesome');
|
|
16
|
-
var freeBrandsSvgIcons = require('@fortawesome/free-brands-svg-icons');
|
|
17
17
|
var Action = require('../../components/auth/Action.js');
|
|
18
18
|
var Message = require('../../components/sign/Message.js');
|
|
19
19
|
var Export = require('../../components/export/Export.js');
|
|
@@ -45,7 +45,7 @@ var helpers = require('../../components/send-transaction/helpers.js');
|
|
|
45
45
|
* wallet management, and user profile operations, as well as UI handlers for modal-driven flows.
|
|
46
46
|
*
|
|
47
47
|
* Features:
|
|
48
|
-
* - Passkey, Wallet, OTP (Email/SMS), and OAuth (Google, Apple, Facebook) authentication and sign-up flows.
|
|
48
|
+
* - Passkey, Wallet, OTP (Email/SMS), and OAuth (Google, Apple, Facebook, X, Discord) authentication and sign-up flows.
|
|
49
49
|
* - Session management: creation, expiration scheduling, refresh, and clearing.
|
|
50
50
|
* - Wallet management: fetch, connect, import, export, account management.
|
|
51
51
|
* - User profile management: email, phone, name, OAuth provider, and passkey linking/removal.
|
|
@@ -85,277 +85,280 @@ const ClientProvider = ({
|
|
|
85
85
|
const {
|
|
86
86
|
isMobile,
|
|
87
87
|
pushPage,
|
|
88
|
+
popPage,
|
|
88
89
|
closeModal
|
|
89
90
|
} = Hook.useModal();
|
|
90
91
|
const completeRedirectOauth = async () => {
|
|
91
92
|
// Check for either hash or search parameters that could indicate an OAuth redirect
|
|
92
|
-
if (window.location.hash
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
if (!window.location.hash && !window.location.search) {
|
|
94
|
+
// No OAuth redirect parameters found, nothing to do
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Wraps an OAuth completion action with optional modal UI.
|
|
99
|
+
* This is the key difference between redirect and popup flows:
|
|
100
|
+
* - Popup: No modal needed (the popup window itself is the UI)
|
|
101
|
+
* - Redirect: Optional modal to show loading/success states on return
|
|
102
|
+
*/
|
|
103
|
+
const withModalWrapper = async params => {
|
|
104
|
+
const {
|
|
105
|
+
provider,
|
|
106
|
+
isAddProvider,
|
|
107
|
+
metadata,
|
|
108
|
+
openModal,
|
|
109
|
+
action
|
|
110
|
+
} = params;
|
|
111
|
+
const providerDisplayName = oauth.capitalizeProviderName(provider);
|
|
112
|
+
const icon = oauth.getProviderIcon(provider);
|
|
113
|
+
if (openModal === "true") {
|
|
114
|
+
// Show modal UI for the completion
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
pushPage({
|
|
117
|
+
key: `${providerDisplayName} OAuth`,
|
|
118
|
+
content: jsxRuntime.jsx(Action.ActionPage, {
|
|
119
|
+
closeOnComplete: isAddProvider ? false : true,
|
|
120
|
+
title: isAddProvider ? `Adding ${providerDisplayName} provider...` : `Authenticating with ${providerDisplayName}...`,
|
|
121
|
+
action: async () => {
|
|
122
|
+
try {
|
|
123
|
+
await action();
|
|
124
|
+
if (isAddProvider && metadata) {
|
|
125
|
+
// Don't show success for auth. Not needed
|
|
121
126
|
pushPage({
|
|
122
|
-
key:
|
|
123
|
-
content: jsxRuntime.jsx(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
publicKey,
|
|
130
|
-
providerName: "facebook",
|
|
131
|
-
...(sessionKey && {
|
|
132
|
-
sessionKey
|
|
133
|
-
})
|
|
134
|
-
});
|
|
135
|
-
// Clean up the URL after processing
|
|
136
|
-
window.history.replaceState(null, document.title, window.location.pathname);
|
|
137
|
-
resolve();
|
|
138
|
-
} catch (err) {
|
|
139
|
-
reject(err);
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
icon: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
|
|
143
|
-
size: "3x",
|
|
144
|
-
icon: freeBrandsSvgIcons.faFacebook
|
|
145
|
-
})
|
|
127
|
+
key: "OAuth Provider Added",
|
|
128
|
+
content: jsxRuntime.jsx(Success.SuccessPage, {
|
|
129
|
+
text: `Successfully added ${providerDisplayName} OAuth provider!`,
|
|
130
|
+
duration: metadata.successPageDuration ?? 2000,
|
|
131
|
+
onComplete: () => {
|
|
132
|
+
closeModal();
|
|
133
|
+
}
|
|
146
134
|
}),
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
reject(new sdkTypes.TurnkeyError("User canceled the Facebook authentication process.", sdkTypes.TurnkeyErrorCodes.USER_CANCELED));
|
|
150
|
-
}
|
|
135
|
+
preventBack: true,
|
|
136
|
+
showTitle: false
|
|
151
137
|
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
138
|
+
}
|
|
139
|
+
resolve();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (isAddProvider) {
|
|
142
|
+
oauth.clearOAuthAddProviderMetadata();
|
|
143
|
+
}
|
|
144
|
+
reject(err);
|
|
145
|
+
popPage();
|
|
158
146
|
}
|
|
159
|
-
}
|
|
147
|
+
},
|
|
148
|
+
icon: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
|
|
149
|
+
icon: icon,
|
|
150
|
+
size: "3x"
|
|
151
|
+
})
|
|
152
|
+
}),
|
|
153
|
+
showTitle: false,
|
|
154
|
+
onClose: () => {
|
|
155
|
+
if (isAddProvider) {
|
|
156
|
+
oauth.clearOAuthAddProviderMetadata();
|
|
157
|
+
}
|
|
158
|
+
reject(new sdkTypes.TurnkeyError(isAddProvider ? `User canceled the ${providerDisplayName} add provider process.` : `User canceled the ${providerDisplayName} authentication process.`, sdkTypes.TurnkeyErrorCodes.USER_CANCELED));
|
|
160
159
|
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
// No modal - execute directly
|
|
164
|
+
try {
|
|
165
|
+
await action();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (isAddProvider) {
|
|
168
|
+
oauth.clearOAuthAddProviderMetadata();
|
|
161
169
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
onClose: () => {
|
|
214
|
-
reject(new sdkTypes.TurnkeyError("User canceled the Discord authentication process.", sdkTypes.TurnkeyErrorCodes.USER_CANCELED));
|
|
215
|
-
}
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
// Handle PKCE-based OAuth redirects (Facebook, Discord, X) with code in search parameters
|
|
175
|
+
if (window.location.search && window.location.search.includes("code=") && window.location.search.includes("state=")) {
|
|
176
|
+
// Parse the URL using our unified helper
|
|
177
|
+
const result = oauth.parseOAuthResponse(window.location.href);
|
|
178
|
+
if (!result || !result.authCode || !result.publicKey) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (result.flow !== "redirect") {
|
|
182
|
+
// To complete OAuth we need redirect flow
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const {
|
|
186
|
+
authCode: code,
|
|
187
|
+
provider,
|
|
188
|
+
publicKey,
|
|
189
|
+
oauthIntent,
|
|
190
|
+
sessionKey,
|
|
191
|
+
nonce,
|
|
192
|
+
openModal
|
|
193
|
+
} = result;
|
|
194
|
+
const isAddProvider = oauthIntent === oauth.OAUTH_INTENT_ADD_PROVIDER;
|
|
195
|
+
const metadata = isAddProvider ? oauth.getOAuthAddProviderMetadata() : null;
|
|
196
|
+
/**
|
|
197
|
+
* Helper to complete PKCE redirect flow with optional modal wrapper.
|
|
198
|
+
* Uses handlePKCEFlow from oauth utils for the core logic.
|
|
199
|
+
* We put this in a separate function to avoid duplicating for each provider.
|
|
200
|
+
*/
|
|
201
|
+
const completePKCERedirect = async (providerName, exchangeCodeFn) => {
|
|
202
|
+
const action = async () => {
|
|
203
|
+
try {
|
|
204
|
+
await oauth.handlePKCEFlow({
|
|
205
|
+
publicKey,
|
|
206
|
+
providerName,
|
|
207
|
+
sessionKey: sessionKey ?? undefined,
|
|
208
|
+
callbacks,
|
|
209
|
+
completeOauth,
|
|
210
|
+
onAddProvider:
|
|
211
|
+
// Only set onAddProvider if we are adding a provider and have metadata
|
|
212
|
+
isAddProvider && metadata ? async oidcToken => {
|
|
213
|
+
await addOauthProvider({
|
|
214
|
+
providerName: provider,
|
|
215
|
+
oidcToken,
|
|
216
|
+
organizationId: metadata.organizationId,
|
|
217
|
+
userId: metadata.userId,
|
|
218
|
+
...(metadata.stampWith && {
|
|
219
|
+
stampWith: metadata.stampWith
|
|
220
|
+
})
|
|
216
221
|
});
|
|
217
|
-
|
|
222
|
+
oauth.clearOAuthAddProviderMetadata();
|
|
223
|
+
} : undefined,
|
|
224
|
+
exchangeCodeForToken: exchangeCodeFn
|
|
225
|
+
});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (callbacks?.onError) {
|
|
228
|
+
const providerDisplayName = oauth.capitalizeProviderName(providerName);
|
|
229
|
+
callbacks.onError(err instanceof sdkTypes.TurnkeyError ? err : new sdkTypes.TurnkeyError(`${providerDisplayName} authentication failed`, sdkTypes.TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, err));
|
|
218
230
|
}
|
|
231
|
+
throw err;
|
|
219
232
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
sessionStorage.removeItem("twitter_verifier");
|
|
243
|
-
const oidcToken = resp?.oidcToken;
|
|
244
|
-
if (!oidcToken) {
|
|
245
|
-
throw new sdkTypes.TurnkeyError("Missing OIDC token", sdkTypes.TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
|
|
246
|
-
}
|
|
247
|
-
await completeOauth({
|
|
248
|
-
oidcToken,
|
|
249
|
-
publicKey,
|
|
250
|
-
providerName: "twitter",
|
|
251
|
-
...(sessionKey && {
|
|
252
|
-
sessionKey
|
|
253
|
-
})
|
|
254
|
-
});
|
|
255
|
-
// Clean up the URL after processing
|
|
256
|
-
window.history.replaceState(null, document.title, window.location.pathname);
|
|
257
|
-
resolve(null);
|
|
258
|
-
} catch (err) {
|
|
259
|
-
reject(err);
|
|
260
|
-
if (callbacks?.onError) {
|
|
261
|
-
callbacks.onError(err instanceof sdkTypes.TurnkeyError ? err : new sdkTypes.TurnkeyError("Twitter authentication failed", sdkTypes.TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, err));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
icon: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
|
|
266
|
-
size: "3x",
|
|
267
|
-
icon: freeBrandsSvgIcons.faXTwitter
|
|
268
|
-
})
|
|
269
|
-
}),
|
|
270
|
-
showTitle: false,
|
|
271
|
-
onClose: () => {
|
|
272
|
-
reject(new sdkTypes.TurnkeyError("User canceled the Twitter authentication process.", sdkTypes.TurnkeyErrorCodes.USER_CANCELED));
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
});
|
|
233
|
+
};
|
|
234
|
+
await withModalWrapper({
|
|
235
|
+
provider: providerName,
|
|
236
|
+
isAddProvider,
|
|
237
|
+
metadata,
|
|
238
|
+
openModal,
|
|
239
|
+
action
|
|
240
|
+
});
|
|
241
|
+
// Clean up URL after successful completion
|
|
242
|
+
oauth.cleanupOAuthUrl();
|
|
243
|
+
};
|
|
244
|
+
// FACEBOOK
|
|
245
|
+
if (provider === sdkTypes.OAuthProviders.FACEBOOK) {
|
|
246
|
+
const clientId = masterConfig?.auth?.oauthConfig?.facebookClientId;
|
|
247
|
+
const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
|
|
248
|
+
const hasVerifier = oauth.hasPKCEVerifier(sdkTypes.OAuthProviders.FACEBOOK);
|
|
249
|
+
if (clientId && redirectURI && hasVerifier) {
|
|
250
|
+
await completePKCERedirect(sdkTypes.OAuthProviders.FACEBOOK, async codeVerifier => {
|
|
251
|
+
const tokenResponse = await oauth.exchangeFacebookCodeForToken(clientId, redirectURI, code, codeVerifier);
|
|
252
|
+
const oidcToken = tokenResponse?.id_token;
|
|
253
|
+
if (!oidcToken) {
|
|
254
|
+
throw new sdkTypes.TurnkeyError("Missing OIDC token", sdkTypes.TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
|
|
276
255
|
}
|
|
277
|
-
|
|
256
|
+
return oidcToken;
|
|
257
|
+
});
|
|
278
258
|
}
|
|
259
|
+
return;
|
|
279
260
|
}
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const providerName = provider ? provider.charAt(0).toUpperCase() + provider.slice(1) : "Provider";
|
|
295
|
-
// Determine which icon to show based on the provider
|
|
296
|
-
let icon;
|
|
297
|
-
if (provider === "apple") {
|
|
298
|
-
icon = jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
|
|
299
|
-
size: "3x",
|
|
300
|
-
icon: freeBrandsSvgIcons.faApple
|
|
301
|
-
});
|
|
302
|
-
} else {
|
|
303
|
-
// Default to Google icon
|
|
304
|
-
icon = jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
|
|
305
|
-
size: "3x",
|
|
306
|
-
icon: freeBrandsSvgIcons.faGoogle
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
// This state is set when the OAuth flow comes from the AuthComponent
|
|
310
|
-
await new Promise((resolve, reject) => {
|
|
311
|
-
pushPage({
|
|
312
|
-
key: `${providerName} OAuth`,
|
|
313
|
-
content: jsxRuntime.jsx(Action.ActionPage, {
|
|
314
|
-
title: `Authenticating with ${providerName}...`,
|
|
315
|
-
action: async () => {
|
|
316
|
-
try {
|
|
317
|
-
await completeOauth({
|
|
318
|
-
oidcToken: idToken,
|
|
319
|
-
publicKey,
|
|
320
|
-
...(provider ? {
|
|
321
|
-
providerName: provider
|
|
322
|
-
} : {}),
|
|
323
|
-
...(sessionKey && {
|
|
324
|
-
sessionKey
|
|
325
|
-
})
|
|
326
|
-
});
|
|
327
|
-
resolve(null);
|
|
328
|
-
} catch (err) {
|
|
329
|
-
reject(err);
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
icon: icon
|
|
333
|
-
}),
|
|
334
|
-
showTitle: false,
|
|
335
|
-
onClose: () => {
|
|
336
|
-
reject(new sdkTypes.TurnkeyError(`User canceled the ${providerName} authentication process.`, sdkTypes.TurnkeyErrorCodes.USER_CANCELED));
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
341
|
-
callbacks.onOauthRedirect({
|
|
342
|
-
idToken,
|
|
343
|
-
publicKey
|
|
261
|
+
// DISCORD
|
|
262
|
+
if (provider === sdkTypes.OAuthProviders.DISCORD) {
|
|
263
|
+
const clientId = masterConfig?.auth?.oauthConfig?.discordClientId;
|
|
264
|
+
const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
|
|
265
|
+
const hasVerifier = oauth.hasPKCEVerifier(sdkTypes.OAuthProviders.DISCORD);
|
|
266
|
+
if (clientId && redirectURI && hasVerifier && nonce) {
|
|
267
|
+
await completePKCERedirect(sdkTypes.OAuthProviders.DISCORD, async codeVerifier => {
|
|
268
|
+
const resp = await client?.httpClient.proxyOAuth2Authenticate({
|
|
269
|
+
provider: "OAUTH2_PROVIDER_DISCORD",
|
|
270
|
+
authCode: code,
|
|
271
|
+
redirectUri: redirectURI,
|
|
272
|
+
codeVerifier,
|
|
273
|
+
clientId,
|
|
274
|
+
nonce
|
|
344
275
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
276
|
+
const oidcToken = resp?.oidcToken;
|
|
277
|
+
if (!oidcToken) {
|
|
278
|
+
throw new sdkTypes.TurnkeyError("Missing OIDC token", sdkTypes.TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
|
|
279
|
+
}
|
|
280
|
+
return oidcToken;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// X (Twitter)
|
|
286
|
+
if (provider === sdkTypes.OAuthProviders.X) {
|
|
287
|
+
const clientId = masterConfig?.auth?.oauthConfig?.xClientId;
|
|
288
|
+
const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
|
|
289
|
+
const hasVerifier = oauth.hasPKCEVerifier(sdkTypes.OAuthProviders.X);
|
|
290
|
+
if (clientId && redirectURI && hasVerifier && nonce) {
|
|
291
|
+
await completePKCERedirect(sdkTypes.OAuthProviders.X, async codeVerifier => {
|
|
292
|
+
const resp = await client?.httpClient.proxyOAuth2Authenticate({
|
|
293
|
+
provider: "OAUTH2_PROVIDER_X",
|
|
294
|
+
authCode: code,
|
|
295
|
+
redirectUri: redirectURI,
|
|
296
|
+
codeVerifier,
|
|
297
|
+
clientId,
|
|
298
|
+
nonce
|
|
352
299
|
});
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
300
|
+
const oidcToken = resp?.oidcToken;
|
|
301
|
+
if (!oidcToken) {
|
|
302
|
+
throw new sdkTypes.TurnkeyError("Missing OIDC token", sdkTypes.TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
|
|
303
|
+
}
|
|
304
|
+
return oidcToken;
|
|
305
|
+
});
|
|
356
306
|
}
|
|
307
|
+
return;
|
|
357
308
|
}
|
|
358
309
|
}
|
|
310
|
+
// Handle Google/Apple redirects (uses hash with idToken - non-PKCE)
|
|
311
|
+
if (window.location.hash) {
|
|
312
|
+
// Parse the URL using our unified helper
|
|
313
|
+
const result = oauth.parseOAuthResponse(window.location.href);
|
|
314
|
+
if (!result || !result.idToken || result.flow !== "redirect" || !result.publicKey) {
|
|
315
|
+
// idToken and publicKey are required to complete OAuth. These are both in the hash for non-PKCE providers
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const {
|
|
319
|
+
idToken,
|
|
320
|
+
provider,
|
|
321
|
+
publicKey,
|
|
322
|
+
openModal,
|
|
323
|
+
sessionKey,
|
|
324
|
+
oauthIntent
|
|
325
|
+
} = result;
|
|
326
|
+
const isAddProvider = oauthIntent === oauth.OAUTH_INTENT_ADD_PROVIDER;
|
|
327
|
+
const metadata = isAddProvider ? oauth.getOAuthAddProviderMetadata() : null;
|
|
328
|
+
const resolvedProvider = provider || sdkTypes.OAuthProviders.GOOGLE;
|
|
329
|
+
// Use completeOAuthFlow from utils for the core completion logic
|
|
330
|
+
const action = async () => {
|
|
331
|
+
await oauth.completeOAuthFlow({
|
|
332
|
+
provider: resolvedProvider,
|
|
333
|
+
publicKey,
|
|
334
|
+
oidcToken: idToken,
|
|
335
|
+
sessionKey: sessionKey ?? undefined,
|
|
336
|
+
callbacks,
|
|
337
|
+
completeOauth,
|
|
338
|
+
onAddProvider: isAddProvider && metadata ? async oidcToken => {
|
|
339
|
+
await addOauthProvider({
|
|
340
|
+
providerName: resolvedProvider,
|
|
341
|
+
oidcToken,
|
|
342
|
+
organizationId: metadata.organizationId,
|
|
343
|
+
userId: metadata.userId,
|
|
344
|
+
...(metadata.stampWith && {
|
|
345
|
+
stampWith: metadata.stampWith
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
oauth.clearOAuthAddProviderMetadata();
|
|
349
|
+
} : undefined
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
await withModalWrapper({
|
|
353
|
+
provider: resolvedProvider,
|
|
354
|
+
isAddProvider,
|
|
355
|
+
metadata,
|
|
356
|
+
openModal: openModal ?? undefined,
|
|
357
|
+
action
|
|
358
|
+
});
|
|
359
|
+
// Clean up the URL after processing
|
|
360
|
+
oauth.cleanupOAuthUrlPreserveSearch();
|
|
361
|
+
}
|
|
359
362
|
};
|
|
360
363
|
const buildConfig = proxyAuthConfig => {
|
|
361
364
|
// Juggle the local overrides with the values set in the dashboard (proxyAuthConfig).
|
|
@@ -1767,139 +1770,94 @@ const ClientProvider = ({
|
|
|
1767
1770
|
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false,
|
|
1768
1771
|
additionalState: additionalParameters
|
|
1769
1772
|
} = params || {};
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
const authCode = urlParams.get("code");
|
|
1839
|
-
const stateParam = urlParams.get("state");
|
|
1840
|
-
const sessionKey = stateParam?.split("&").find(param => param.startsWith("sessionKey="))?.split("=")[1];
|
|
1841
|
-
if (authCode && stateParam && stateParam.includes("provider=discord")) {
|
|
1842
|
-
authWindow.close();
|
|
1843
|
-
clearInterval(interval);
|
|
1844
|
-
const verifier = sessionStorage.getItem("discord_verifier");
|
|
1845
|
-
if (!verifier) {
|
|
1846
|
-
reject(new Error("Missing PKCE verifier"));
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
client?.httpClient.proxyOAuth2Authenticate({
|
|
1773
|
+
const provider = sdkTypes.OAuthProviders.DISCORD;
|
|
1774
|
+
if (!masterConfig) {
|
|
1775
|
+
throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1776
|
+
}
|
|
1777
|
+
if (!clientId) {
|
|
1778
|
+
throw new sdkTypes.TurnkeyError("Discord Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1779
|
+
}
|
|
1780
|
+
if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
|
|
1781
|
+
throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1782
|
+
}
|
|
1783
|
+
const flow = openInPage ? "redirect" : "popup";
|
|
1784
|
+
const redirectUri = masterConfig.auth?.oauthConfig.oauthRedirectUri;
|
|
1785
|
+
// Create key pair and generate nonce
|
|
1786
|
+
const publicKey = await createApiKeyPair();
|
|
1787
|
+
if (!publicKey) {
|
|
1788
|
+
throw new Error("Failed to create public key for OAuth.");
|
|
1789
|
+
}
|
|
1790
|
+
const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
|
|
1791
|
+
// Generate PKCE challenge pair and store verifier
|
|
1792
|
+
const {
|
|
1793
|
+
verifier,
|
|
1794
|
+
codeChallenge
|
|
1795
|
+
} = await oauth.generateChallengePair();
|
|
1796
|
+
oauth.storePKCEVerifier(provider, verifier);
|
|
1797
|
+
// Build OAuth URL
|
|
1798
|
+
const authUrl = oauth.buildOAuthUrl({
|
|
1799
|
+
provider,
|
|
1800
|
+
clientId,
|
|
1801
|
+
redirectUri,
|
|
1802
|
+
publicKey,
|
|
1803
|
+
nonce,
|
|
1804
|
+
flow,
|
|
1805
|
+
codeChallenge,
|
|
1806
|
+
additionalState: additionalParameters
|
|
1807
|
+
});
|
|
1808
|
+
if (openInPage) {
|
|
1809
|
+
// Remainder of logic will occur in completeRedirectOauth
|
|
1810
|
+
return oauth.redirectToOAuthProvider(authUrl);
|
|
1811
|
+
}
|
|
1812
|
+
// Popup flow
|
|
1813
|
+
const authWindow = oauth.openOAuthPopup();
|
|
1814
|
+
if (!authWindow) {
|
|
1815
|
+
throw new Error(`Failed to open ${oauth.capitalizeProviderName(provider)} login window.`);
|
|
1816
|
+
}
|
|
1817
|
+
authWindow.location.href = authUrl;
|
|
1818
|
+
return new Promise((resolve, reject) => {
|
|
1819
|
+
const interval = setInterval(() => {
|
|
1820
|
+
try {
|
|
1821
|
+
if (authWindow.closed) {
|
|
1822
|
+
clearInterval(interval);
|
|
1823
|
+
reject(new Error("Authentication window was closed."));
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
const url = authWindow.location.href || "";
|
|
1827
|
+
if (url.startsWith(window.location.origin)) {
|
|
1828
|
+
const result = oauth.parseOAuthResponse(url, provider);
|
|
1829
|
+
if (result) {
|
|
1830
|
+
authWindow.close();
|
|
1831
|
+
clearInterval(interval);
|
|
1832
|
+
oauth.completeOAuthPopup({
|
|
1833
|
+
provider,
|
|
1834
|
+
publicKey,
|
|
1835
|
+
result,
|
|
1836
|
+
callbacks,
|
|
1837
|
+
completeOauth,
|
|
1838
|
+
onOauthSuccess: params?.onOauthSuccess,
|
|
1839
|
+
exchangeCodeForToken: async codeVerifier => {
|
|
1840
|
+
const resp = await client?.httpClient.proxyOAuth2Authenticate({
|
|
1850
1841
|
provider: "OAUTH2_PROVIDER_DISCORD",
|
|
1851
|
-
authCode,
|
|
1852
|
-
redirectUri
|
|
1853
|
-
codeVerifier
|
|
1842
|
+
authCode: result.authCode,
|
|
1843
|
+
redirectUri,
|
|
1844
|
+
codeVerifier,
|
|
1854
1845
|
clientId,
|
|
1855
|
-
nonce
|
|
1856
|
-
})
|
|
1857
|
-
|
|
1858
|
-
const oidcToken = resp.oidcToken;
|
|
1859
|
-
if (params?.onOauthSuccess) {
|
|
1860
|
-
params.onOauthSuccess({
|
|
1861
|
-
publicKey,
|
|
1862
|
-
oidcToken,
|
|
1863
|
-
providerName: "discord",
|
|
1864
|
-
...(sessionKey && {
|
|
1865
|
-
sessionKey
|
|
1866
|
-
})
|
|
1867
|
-
});
|
|
1868
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
1869
|
-
callbacks.onOauthRedirect({
|
|
1870
|
-
idToken: oidcToken,
|
|
1871
|
-
publicKey,
|
|
1872
|
-
...(sessionKey && {
|
|
1873
|
-
sessionKey
|
|
1874
|
-
})
|
|
1875
|
-
});
|
|
1876
|
-
} else {
|
|
1877
|
-
completeOauth({
|
|
1878
|
-
oidcToken,
|
|
1879
|
-
publicKey,
|
|
1880
|
-
providerName: "discord",
|
|
1881
|
-
...(sessionKey && {
|
|
1882
|
-
sessionKey
|
|
1883
|
-
})
|
|
1884
|
-
}).then(() => resolve()).catch(reject);
|
|
1885
|
-
return;
|
|
1886
|
-
}
|
|
1887
|
-
resolve();
|
|
1888
|
-
}).catch(reject);
|
|
1846
|
+
nonce
|
|
1847
|
+
});
|
|
1848
|
+
return resp?.oidcToken ?? "";
|
|
1889
1849
|
}
|
|
1890
|
-
}
|
|
1891
|
-
} catch {
|
|
1892
|
-
// ignore cross-origin
|
|
1850
|
+
}).then(() => resolve()).catch(reject);
|
|
1893
1851
|
}
|
|
1894
|
-
}, 500);
|
|
1895
|
-
if (authWindow.closed) {
|
|
1896
|
-
clearInterval(interval);
|
|
1897
1852
|
}
|
|
1898
|
-
}
|
|
1853
|
+
} catch {
|
|
1854
|
+
// ignore cross-origin
|
|
1855
|
+
}
|
|
1856
|
+
}, 500);
|
|
1857
|
+
if (authWindow.closed) {
|
|
1858
|
+
clearInterval(interval);
|
|
1899
1859
|
}
|
|
1900
|
-
}
|
|
1901
|
-
throw error;
|
|
1902
|
-
}
|
|
1860
|
+
});
|
|
1903
1861
|
}, [client, callbacks, completeOauth, createApiKeyPair, masterConfig]);
|
|
1904
1862
|
const handleXOauth = react.useCallback(async params => {
|
|
1905
1863
|
const {
|
|
@@ -1907,139 +1865,94 @@ const ClientProvider = ({
|
|
|
1907
1865
|
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false,
|
|
1908
1866
|
additionalState: additionalParameters
|
|
1909
1867
|
} = params || {};
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
const authCode = urlParams.get("code");
|
|
1979
|
-
const stateParam = urlParams.get("state");
|
|
1980
|
-
const sessionKey = stateParam?.split("&").find(param => param.startsWith("sessionKey="))?.split("=")[1];
|
|
1981
|
-
if (authCode && stateParam && stateParam.includes("provider=twitter")) {
|
|
1982
|
-
authWindow.close();
|
|
1983
|
-
clearInterval(interval);
|
|
1984
|
-
const verifier = sessionStorage.getItem("twitter_verifier");
|
|
1985
|
-
if (!verifier) {
|
|
1986
|
-
reject(new Error("Missing PKCE verifier"));
|
|
1987
|
-
return;
|
|
1988
|
-
}
|
|
1989
|
-
client?.httpClient.proxyOAuth2Authenticate({
|
|
1868
|
+
const provider = sdkTypes.OAuthProviders.X;
|
|
1869
|
+
if (!masterConfig) {
|
|
1870
|
+
throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1871
|
+
}
|
|
1872
|
+
if (!clientId) {
|
|
1873
|
+
throw new sdkTypes.TurnkeyError("Twitter Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1874
|
+
}
|
|
1875
|
+
if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
|
|
1876
|
+
throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1877
|
+
}
|
|
1878
|
+
const flow = openInPage ? "redirect" : "popup";
|
|
1879
|
+
const redirectUri = masterConfig.auth?.oauthConfig.oauthRedirectUri;
|
|
1880
|
+
// Create key pair and generate nonce
|
|
1881
|
+
const publicKey = await createApiKeyPair();
|
|
1882
|
+
if (!publicKey) {
|
|
1883
|
+
throw new Error("Failed to create public key for OAuth.");
|
|
1884
|
+
}
|
|
1885
|
+
const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
|
|
1886
|
+
// Generate PKCE challenge pair and store verifier
|
|
1887
|
+
const {
|
|
1888
|
+
verifier,
|
|
1889
|
+
codeChallenge
|
|
1890
|
+
} = await oauth.generateChallengePair();
|
|
1891
|
+
oauth.storePKCEVerifier(provider, verifier);
|
|
1892
|
+
// Build OAuth URL
|
|
1893
|
+
const authUrl = oauth.buildOAuthUrl({
|
|
1894
|
+
provider,
|
|
1895
|
+
clientId,
|
|
1896
|
+
redirectUri,
|
|
1897
|
+
publicKey,
|
|
1898
|
+
nonce,
|
|
1899
|
+
flow,
|
|
1900
|
+
codeChallenge,
|
|
1901
|
+
additionalState: additionalParameters
|
|
1902
|
+
});
|
|
1903
|
+
if (openInPage) {
|
|
1904
|
+
// Remainder of logic will occur in completeRedirectOauth
|
|
1905
|
+
return oauth.redirectToOAuthProvider(authUrl);
|
|
1906
|
+
}
|
|
1907
|
+
// Popup flow
|
|
1908
|
+
const authWindow = oauth.openOAuthPopup();
|
|
1909
|
+
if (!authWindow) {
|
|
1910
|
+
throw new Error(`Failed to open ${oauth.capitalizeProviderName(provider)} login window.`);
|
|
1911
|
+
}
|
|
1912
|
+
authWindow.location.href = authUrl;
|
|
1913
|
+
return new Promise((resolve, reject) => {
|
|
1914
|
+
const interval = setInterval(() => {
|
|
1915
|
+
try {
|
|
1916
|
+
if (authWindow.closed) {
|
|
1917
|
+
clearInterval(interval);
|
|
1918
|
+
reject(new Error("Authentication window was closed."));
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
const url = authWindow.location.href || "";
|
|
1922
|
+
if (url.startsWith(window.location.origin)) {
|
|
1923
|
+
const result = oauth.parseOAuthResponse(url, provider);
|
|
1924
|
+
if (result) {
|
|
1925
|
+
authWindow.close();
|
|
1926
|
+
clearInterval(interval);
|
|
1927
|
+
oauth.completeOAuthPopup({
|
|
1928
|
+
provider,
|
|
1929
|
+
publicKey,
|
|
1930
|
+
result,
|
|
1931
|
+
callbacks,
|
|
1932
|
+
completeOauth,
|
|
1933
|
+
onOauthSuccess: params?.onOauthSuccess,
|
|
1934
|
+
exchangeCodeForToken: async codeVerifier => {
|
|
1935
|
+
const resp = await client?.httpClient.proxyOAuth2Authenticate({
|
|
1990
1936
|
provider: "OAUTH2_PROVIDER_X",
|
|
1991
|
-
authCode,
|
|
1992
|
-
redirectUri
|
|
1993
|
-
codeVerifier
|
|
1937
|
+
authCode: result.authCode,
|
|
1938
|
+
redirectUri,
|
|
1939
|
+
codeVerifier,
|
|
1994
1940
|
clientId,
|
|
1995
1941
|
nonce
|
|
1996
|
-
})
|
|
1997
|
-
|
|
1998
|
-
const oidcToken = resp.oidcToken;
|
|
1999
|
-
if (params?.onOauthSuccess) {
|
|
2000
|
-
params.onOauthSuccess({
|
|
2001
|
-
publicKey,
|
|
2002
|
-
oidcToken,
|
|
2003
|
-
providerName: "twitter",
|
|
2004
|
-
...(sessionKey && {
|
|
2005
|
-
sessionKey
|
|
2006
|
-
})
|
|
2007
|
-
});
|
|
2008
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
2009
|
-
callbacks.onOauthRedirect({
|
|
2010
|
-
idToken: oidcToken,
|
|
2011
|
-
publicKey,
|
|
2012
|
-
...(sessionKey && {
|
|
2013
|
-
sessionKey
|
|
2014
|
-
})
|
|
2015
|
-
});
|
|
2016
|
-
} else {
|
|
2017
|
-
completeOauth({
|
|
2018
|
-
oidcToken,
|
|
2019
|
-
publicKey,
|
|
2020
|
-
providerName: "twitter",
|
|
2021
|
-
...(sessionKey && {
|
|
2022
|
-
sessionKey
|
|
2023
|
-
})
|
|
2024
|
-
}).then(() => resolve()).catch(reject);
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
resolve();
|
|
2028
|
-
}).catch(reject);
|
|
1942
|
+
});
|
|
1943
|
+
return resp?.oidcToken ?? "";
|
|
2029
1944
|
}
|
|
2030
|
-
}
|
|
2031
|
-
} catch {
|
|
2032
|
-
// ignore cross-origin
|
|
1945
|
+
}).then(() => resolve()).catch(reject);
|
|
2033
1946
|
}
|
|
2034
|
-
}, 500);
|
|
2035
|
-
if (authWindow.closed) {
|
|
2036
|
-
clearInterval(interval);
|
|
2037
1947
|
}
|
|
2038
|
-
}
|
|
1948
|
+
} catch {
|
|
1949
|
+
// ignore cross-origin
|
|
1950
|
+
}
|
|
1951
|
+
}, 500);
|
|
1952
|
+
if (authWindow.closed) {
|
|
1953
|
+
clearInterval(interval);
|
|
2039
1954
|
}
|
|
2040
|
-
}
|
|
2041
|
-
throw error;
|
|
2042
|
-
}
|
|
1955
|
+
});
|
|
2043
1956
|
}, [client, callbacks, completeOauth, createApiKeyPair, masterConfig]);
|
|
2044
1957
|
const handleGoogleOauth = react.useCallback(async params => {
|
|
2045
1958
|
const {
|
|
@@ -2047,128 +1960,77 @@ const ClientProvider = ({
|
|
|
2047
1960
|
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false,
|
|
2048
1961
|
additionalState: additionalParameters
|
|
2049
1962
|
} = params || {};
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const timeout = setTimeout(() => {
|
|
2093
|
-
reject(new Error("Authentication timed out."));
|
|
2094
|
-
}, 300000); // 5 minutes
|
|
2095
|
-
// If the page is unloaded (user navigates away), clear the timeout
|
|
2096
|
-
window.addEventListener("beforeunload", () => clearTimeout(timeout));
|
|
2097
|
-
});
|
|
2098
|
-
} else {
|
|
2099
|
-
// Open popup window
|
|
2100
|
-
const width = utils.popupWidth;
|
|
2101
|
-
const height = utils.popupHeight;
|
|
2102
|
-
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
2103
|
-
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
2104
|
-
const authWindow = window.open("about:blank", "_blank", `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`);
|
|
2105
|
-
if (!authWindow) {
|
|
2106
|
-
throw new Error("Failed to open Google login window.");
|
|
2107
|
-
}
|
|
2108
|
-
authWindow.location.href = googleAuthUrl.toString();
|
|
2109
|
-
// Return a promise that resolves when the OAuth flow completes
|
|
2110
|
-
// This following code will only run for the popup flow
|
|
2111
|
-
return new Promise((resolve, reject) => {
|
|
2112
|
-
const interval = setInterval(() => {
|
|
2113
|
-
try {
|
|
2114
|
-
// Check if window was closed without completing auth
|
|
2115
|
-
if (authWindow.closed) {
|
|
2116
|
-
clearInterval(interval);
|
|
2117
|
-
reject(new Error("Authentication window was closed."));
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
const url = authWindow.location.href || "";
|
|
2121
|
-
if (url.startsWith(window.location.origin)) {
|
|
2122
|
-
const hashParams = new URLSearchParams(url.split("#")[1]);
|
|
2123
|
-
const idToken = hashParams.get("id_token");
|
|
2124
|
-
const stateParams = hashParams.get("state");
|
|
2125
|
-
const sessionKey = stateParams?.split("&").find(param => param.startsWith("sessionKey="))?.split("=")[1];
|
|
2126
|
-
if (idToken) {
|
|
2127
|
-
authWindow.close();
|
|
2128
|
-
clearInterval(interval);
|
|
2129
|
-
if (params?.onOauthSuccess) {
|
|
2130
|
-
params.onOauthSuccess({
|
|
2131
|
-
publicKey,
|
|
2132
|
-
oidcToken: idToken,
|
|
2133
|
-
providerName: "google",
|
|
2134
|
-
...(sessionKey && {
|
|
2135
|
-
sessionKey
|
|
2136
|
-
})
|
|
2137
|
-
});
|
|
2138
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
2139
|
-
callbacks.onOauthRedirect({
|
|
2140
|
-
idToken,
|
|
2141
|
-
publicKey,
|
|
2142
|
-
...(sessionKey && {
|
|
2143
|
-
sessionKey
|
|
2144
|
-
})
|
|
2145
|
-
});
|
|
2146
|
-
} else {
|
|
2147
|
-
completeOauth({
|
|
2148
|
-
oidcToken: idToken,
|
|
2149
|
-
publicKey,
|
|
2150
|
-
providerName: "google",
|
|
2151
|
-
...(sessionKey && {
|
|
2152
|
-
sessionKey
|
|
2153
|
-
})
|
|
2154
|
-
}).then(() => resolve()).catch(reject);
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
resolve();
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
} catch (error) {
|
|
2161
|
-
// Ignore cross-origin errors
|
|
2162
|
-
}
|
|
2163
|
-
}, 500);
|
|
1963
|
+
const provider = sdkTypes.OAuthProviders.GOOGLE;
|
|
1964
|
+
if (!masterConfig) {
|
|
1965
|
+
throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1966
|
+
}
|
|
1967
|
+
if (!clientId) {
|
|
1968
|
+
throw new sdkTypes.TurnkeyError("Google Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1969
|
+
}
|
|
1970
|
+
if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
|
|
1971
|
+
throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
1972
|
+
}
|
|
1973
|
+
const flow = openInPage ? "redirect" : "popup";
|
|
1974
|
+
// Google requires no trailing slash
|
|
1975
|
+
const redirectUri = masterConfig.auth?.oauthConfig.oauthRedirectUri.replace(/\/$/, "");
|
|
1976
|
+
// Create key pair and generate nonce
|
|
1977
|
+
const publicKey = await createApiKeyPair();
|
|
1978
|
+
if (!publicKey) {
|
|
1979
|
+
throw new Error("Failed to create public key for OAuth.");
|
|
1980
|
+
}
|
|
1981
|
+
const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
|
|
1982
|
+
// Build OAuth URL
|
|
1983
|
+
const authUrl = oauth.buildOAuthUrl({
|
|
1984
|
+
provider,
|
|
1985
|
+
clientId,
|
|
1986
|
+
redirectUri,
|
|
1987
|
+
publicKey,
|
|
1988
|
+
nonce,
|
|
1989
|
+
flow,
|
|
1990
|
+
additionalState: additionalParameters
|
|
1991
|
+
});
|
|
1992
|
+
if (openInPage) {
|
|
1993
|
+
// Remainder of logic will occur in completeRedirectOauth
|
|
1994
|
+
return oauth.redirectToOAuthProvider(authUrl);
|
|
1995
|
+
}
|
|
1996
|
+
// Popup flow
|
|
1997
|
+
const authWindow = oauth.openOAuthPopup();
|
|
1998
|
+
if (!authWindow) {
|
|
1999
|
+
throw new Error(`Failed to open ${oauth.capitalizeProviderName(provider)} login window.`);
|
|
2000
|
+
}
|
|
2001
|
+
authWindow.location.href = authUrl;
|
|
2002
|
+
return new Promise((resolve, reject) => {
|
|
2003
|
+
const interval = setInterval(() => {
|
|
2004
|
+
try {
|
|
2164
2005
|
if (authWindow.closed) {
|
|
2165
2006
|
clearInterval(interval);
|
|
2007
|
+
reject(new Error("Authentication window was closed."));
|
|
2008
|
+
return;
|
|
2166
2009
|
}
|
|
2167
|
-
|
|
2010
|
+
const url = authWindow.location.href || "";
|
|
2011
|
+
if (url.startsWith(window.location.origin)) {
|
|
2012
|
+
const result = oauth.parseOAuthResponse(url, provider);
|
|
2013
|
+
if (result) {
|
|
2014
|
+
authWindow.close();
|
|
2015
|
+
clearInterval(interval);
|
|
2016
|
+
oauth.completeOAuthPopup({
|
|
2017
|
+
provider,
|
|
2018
|
+
publicKey,
|
|
2019
|
+
result,
|
|
2020
|
+
callbacks,
|
|
2021
|
+
completeOauth,
|
|
2022
|
+
onOauthSuccess: params?.onOauthSuccess
|
|
2023
|
+
}).then(() => resolve()).catch(reject);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
} catch {
|
|
2027
|
+
// Ignore cross-origin errors
|
|
2028
|
+
}
|
|
2029
|
+
}, 500);
|
|
2030
|
+
if (authWindow.closed) {
|
|
2031
|
+
clearInterval(interval);
|
|
2168
2032
|
}
|
|
2169
|
-
}
|
|
2170
|
-
throw error;
|
|
2171
|
-
}
|
|
2033
|
+
});
|
|
2172
2034
|
}, [callbacks, completeOauth, createApiKeyPair, masterConfig]);
|
|
2173
2035
|
const handleAppleOauth = react.useCallback(async params => {
|
|
2174
2036
|
const {
|
|
@@ -2176,124 +2038,77 @@ const ClientProvider = ({
|
|
|
2176
2038
|
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false,
|
|
2177
2039
|
additionalState: additionalParameters
|
|
2178
2040
|
} = params || {};
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
// If the page is unloaded (user navigates away), clear the timeout
|
|
2222
|
-
window.addEventListener("beforeunload", () => clearTimeout(timeout));
|
|
2223
|
-
});
|
|
2224
|
-
} else {
|
|
2225
|
-
// Open popup window
|
|
2226
|
-
const width = utils.popupWidth;
|
|
2227
|
-
const height = utils.popupHeight;
|
|
2228
|
-
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
2229
|
-
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
2230
|
-
const authWindow = window.open("about:blank", "_blank", `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`);
|
|
2231
|
-
if (!authWindow) {
|
|
2232
|
-
throw new Error("Failed to open Apple login window.");
|
|
2233
|
-
}
|
|
2234
|
-
authWindow.location.href = appleAuthUrl.toString();
|
|
2235
|
-
// Return a promise that resolves when the OAuth flow completes
|
|
2236
|
-
return new Promise((resolve, reject) => {
|
|
2237
|
-
const interval = setInterval(() => {
|
|
2238
|
-
try {
|
|
2239
|
-
// Check if window was closed without completing auth
|
|
2240
|
-
if (authWindow.closed) {
|
|
2241
|
-
clearInterval(interval);
|
|
2242
|
-
reject(new Error("Authentication window was closed."));
|
|
2243
|
-
return;
|
|
2244
|
-
}
|
|
2245
|
-
const url = authWindow.location.href || "";
|
|
2246
|
-
if (url.startsWith(window.location.origin)) {
|
|
2247
|
-
const hashParams = new URLSearchParams(url.split("#")[1]);
|
|
2248
|
-
const idToken = hashParams.get("id_token");
|
|
2249
|
-
const stateParams = hashParams.get("state");
|
|
2250
|
-
const sessionKey = stateParams?.split("&").find(param => param.startsWith("sessionKey="))?.split("=")[1];
|
|
2251
|
-
if (idToken) {
|
|
2252
|
-
authWindow.close();
|
|
2253
|
-
clearInterval(interval);
|
|
2254
|
-
if (params?.onOauthSuccess) {
|
|
2255
|
-
params.onOauthSuccess({
|
|
2256
|
-
publicKey,
|
|
2257
|
-
oidcToken: idToken,
|
|
2258
|
-
providerName: "apple",
|
|
2259
|
-
...(sessionKey && {
|
|
2260
|
-
sessionKey
|
|
2261
|
-
})
|
|
2262
|
-
});
|
|
2263
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
2264
|
-
callbacks.onOauthRedirect({
|
|
2265
|
-
idToken,
|
|
2266
|
-
publicKey,
|
|
2267
|
-
...(sessionKey && {
|
|
2268
|
-
sessionKey
|
|
2269
|
-
})
|
|
2270
|
-
});
|
|
2271
|
-
} else {
|
|
2272
|
-
completeOauth({
|
|
2273
|
-
oidcToken: idToken,
|
|
2274
|
-
publicKey,
|
|
2275
|
-
providerName: "apple",
|
|
2276
|
-
...(sessionKey && {
|
|
2277
|
-
sessionKey
|
|
2278
|
-
})
|
|
2279
|
-
}).then(() => resolve()).catch(reject);
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
resolve();
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
} catch (error) {
|
|
2286
|
-
// Ignore cross-origin errors
|
|
2287
|
-
}
|
|
2288
|
-
}, 500);
|
|
2041
|
+
const provider = sdkTypes.OAuthProviders.APPLE;
|
|
2042
|
+
if (!masterConfig) {
|
|
2043
|
+
throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2044
|
+
}
|
|
2045
|
+
if (!clientId) {
|
|
2046
|
+
throw new sdkTypes.TurnkeyError("Apple Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2047
|
+
}
|
|
2048
|
+
if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
|
|
2049
|
+
throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2050
|
+
}
|
|
2051
|
+
const flow = openInPage ? "redirect" : "popup";
|
|
2052
|
+
// TODO (Amir): Apple needs the '/' at the end. Maybe we should add it if not there?
|
|
2053
|
+
const redirectUri = masterConfig.auth?.oauthConfig.oauthRedirectUri;
|
|
2054
|
+
// Create key pair and generate nonce
|
|
2055
|
+
const publicKey = await createApiKeyPair();
|
|
2056
|
+
if (!publicKey) {
|
|
2057
|
+
throw new Error("Failed to create public key for OAuth.");
|
|
2058
|
+
}
|
|
2059
|
+
const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
|
|
2060
|
+
// Build OAuth URL
|
|
2061
|
+
const authUrl = oauth.buildOAuthUrl({
|
|
2062
|
+
provider,
|
|
2063
|
+
clientId,
|
|
2064
|
+
redirectUri,
|
|
2065
|
+
publicKey,
|
|
2066
|
+
nonce,
|
|
2067
|
+
flow,
|
|
2068
|
+
additionalState: additionalParameters
|
|
2069
|
+
});
|
|
2070
|
+
if (openInPage) {
|
|
2071
|
+
// Remainder of logic will occur in completeRedirectOauth
|
|
2072
|
+
return oauth.redirectToOAuthProvider(authUrl);
|
|
2073
|
+
}
|
|
2074
|
+
// Popup flow
|
|
2075
|
+
const authWindow = oauth.openOAuthPopup();
|
|
2076
|
+
if (!authWindow) {
|
|
2077
|
+
throw new Error(`Failed to open ${oauth.capitalizeProviderName(provider)} login window.`);
|
|
2078
|
+
}
|
|
2079
|
+
authWindow.location.href = authUrl;
|
|
2080
|
+
return new Promise((resolve, reject) => {
|
|
2081
|
+
const interval = setInterval(() => {
|
|
2082
|
+
try {
|
|
2289
2083
|
if (authWindow.closed) {
|
|
2290
2084
|
clearInterval(interval);
|
|
2085
|
+
reject(new Error("Authentication window was closed."));
|
|
2086
|
+
return;
|
|
2291
2087
|
}
|
|
2292
|
-
|
|
2088
|
+
const url = authWindow.location.href || "";
|
|
2089
|
+
if (url.startsWith(window.location.origin)) {
|
|
2090
|
+
const result = oauth.parseOAuthResponse(url, provider);
|
|
2091
|
+
if (result) {
|
|
2092
|
+
authWindow.close();
|
|
2093
|
+
clearInterval(interval);
|
|
2094
|
+
oauth.completeOAuthPopup({
|
|
2095
|
+
provider,
|
|
2096
|
+
publicKey,
|
|
2097
|
+
result,
|
|
2098
|
+
callbacks,
|
|
2099
|
+
completeOauth,
|
|
2100
|
+
onOauthSuccess: params?.onOauthSuccess
|
|
2101
|
+
}).then(() => resolve()).catch(reject);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
} catch {
|
|
2105
|
+
// Ignore cross-origin errors
|
|
2106
|
+
}
|
|
2107
|
+
}, 500);
|
|
2108
|
+
if (authWindow.closed) {
|
|
2109
|
+
clearInterval(interval);
|
|
2293
2110
|
}
|
|
2294
|
-
}
|
|
2295
|
-
throw error;
|
|
2296
|
-
}
|
|
2111
|
+
});
|
|
2297
2112
|
}, [callbacks, completeOauth, createApiKeyPair, masterConfig]);
|
|
2298
2113
|
const handleFacebookOauth = react.useCallback(async params => {
|
|
2299
2114
|
const {
|
|
@@ -2301,142 +2116,87 @@ const ClientProvider = ({
|
|
|
2301
2116
|
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false,
|
|
2302
2117
|
additionalState: additionalParameters
|
|
2303
2118
|
} = params || {};
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
const timeout = setTimeout(() => {
|
|
2353
|
-
reject(new Error("Authentication timed out."));
|
|
2354
|
-
}, 300000); // 5 minutes
|
|
2355
|
-
// If the page is unloaded (user navigates away), clear the timeout
|
|
2356
|
-
window.addEventListener("beforeunload", () => clearTimeout(timeout));
|
|
2357
|
-
});
|
|
2358
|
-
} else {
|
|
2359
|
-
// Open popup window
|
|
2360
|
-
const width = utils.popupWidth;
|
|
2361
|
-
const height = utils.popupHeight;
|
|
2362
|
-
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
2363
|
-
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
2364
|
-
const authWindow = window.open("about:blank", "_blank", `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`);
|
|
2365
|
-
if (!authWindow) {
|
|
2366
|
-
throw new Error("Failed to open Facebook login window.");
|
|
2367
|
-
}
|
|
2368
|
-
authWindow.location.href = facebookAuthUrl.toString();
|
|
2369
|
-
// Return a promise that resolves when the OAuth flow completes
|
|
2370
|
-
return new Promise((resolve, reject) => {
|
|
2371
|
-
const interval = setInterval(() => {
|
|
2372
|
-
try {
|
|
2373
|
-
// Check if window was closed without completing auth
|
|
2374
|
-
if (authWindow.closed) {
|
|
2375
|
-
clearInterval(interval);
|
|
2376
|
-
reject(new Error("Authentication window was closed."));
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
const url = authWindow.location.href || "";
|
|
2380
|
-
if (url.startsWith(window.location.origin)) {
|
|
2381
|
-
const urlParams = new URLSearchParams(new URL(url).search);
|
|
2382
|
-
const authCode = urlParams.get("code");
|
|
2383
|
-
const stateParam = urlParams.get("state");
|
|
2384
|
-
if (authCode && stateParam && stateParam.includes("provider=facebook")) {
|
|
2385
|
-
const sessionKey = stateParam?.split("&").find(param => param.startsWith("sessionKey="))?.split("=")[1];
|
|
2386
|
-
authWindow.close();
|
|
2387
|
-
clearInterval(interval);
|
|
2388
|
-
// Exchange code for token
|
|
2389
|
-
const verifier = sessionStorage.getItem("facebook_verifier");
|
|
2390
|
-
if (!verifier) {
|
|
2391
|
-
reject(new Error("Missing PKCE verifier"));
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
|
-
utils.exchangeCodeForToken(clientId, redirectURI, authCode, verifier).then(tokenData => {
|
|
2395
|
-
sessionStorage.removeItem("facebook_verifier");
|
|
2396
|
-
if (params?.onOauthSuccess) {
|
|
2397
|
-
params.onOauthSuccess({
|
|
2398
|
-
publicKey,
|
|
2399
|
-
oidcToken: tokenData.id_token,
|
|
2400
|
-
providerName: "facebook",
|
|
2401
|
-
...(sessionKey && {
|
|
2402
|
-
sessionKey
|
|
2403
|
-
})
|
|
2404
|
-
});
|
|
2405
|
-
} else if (callbacks?.onOauthRedirect) {
|
|
2406
|
-
callbacks.onOauthRedirect({
|
|
2407
|
-
idToken: tokenData.id_token,
|
|
2408
|
-
publicKey,
|
|
2409
|
-
...(sessionKey && {
|
|
2410
|
-
sessionKey
|
|
2411
|
-
})
|
|
2412
|
-
});
|
|
2413
|
-
} else {
|
|
2414
|
-
completeOauth({
|
|
2415
|
-
oidcToken: tokenData.id_token,
|
|
2416
|
-
publicKey,
|
|
2417
|
-
providerName: "facebook",
|
|
2418
|
-
...(sessionKey && {
|
|
2419
|
-
sessionKey
|
|
2420
|
-
})
|
|
2421
|
-
}).then(() => resolve()).catch(reject);
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
resolve();
|
|
2425
|
-
}).catch(reject);
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
} catch (error) {
|
|
2429
|
-
// Ignore cross-origin errors
|
|
2430
|
-
}
|
|
2431
|
-
}, 500);
|
|
2119
|
+
const provider = sdkTypes.OAuthProviders.FACEBOOK;
|
|
2120
|
+
if (!masterConfig) {
|
|
2121
|
+
throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2122
|
+
}
|
|
2123
|
+
if (!clientId) {
|
|
2124
|
+
throw new sdkTypes.TurnkeyError("Facebook Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2125
|
+
}
|
|
2126
|
+
if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
|
|
2127
|
+
throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
|
|
2128
|
+
}
|
|
2129
|
+
const flow = openInPage ? "redirect" : "popup";
|
|
2130
|
+
const redirectUri = masterConfig.auth?.oauthConfig.oauthRedirectUri;
|
|
2131
|
+
// Create key pair and generate nonce
|
|
2132
|
+
const publicKey = await createApiKeyPair();
|
|
2133
|
+
if (!publicKey) {
|
|
2134
|
+
throw new Error("Failed to create public key for OAuth.");
|
|
2135
|
+
}
|
|
2136
|
+
const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
|
|
2137
|
+
// Generate PKCE challenge pair and store verifier
|
|
2138
|
+
const {
|
|
2139
|
+
verifier,
|
|
2140
|
+
codeChallenge
|
|
2141
|
+
} = await oauth.generateChallengePair();
|
|
2142
|
+
oauth.storePKCEVerifier(provider, verifier);
|
|
2143
|
+
// Build OAuth URL
|
|
2144
|
+
const authUrl = oauth.buildOAuthUrl({
|
|
2145
|
+
provider,
|
|
2146
|
+
clientId,
|
|
2147
|
+
redirectUri,
|
|
2148
|
+
publicKey,
|
|
2149
|
+
nonce,
|
|
2150
|
+
flow,
|
|
2151
|
+
codeChallenge,
|
|
2152
|
+
additionalState: additionalParameters
|
|
2153
|
+
});
|
|
2154
|
+
if (openInPage) {
|
|
2155
|
+
// Remainder of logic will occur in completeRedirectOauth
|
|
2156
|
+
return oauth.redirectToOAuthProvider(authUrl);
|
|
2157
|
+
}
|
|
2158
|
+
// Popup flow
|
|
2159
|
+
const authWindow = oauth.openOAuthPopup();
|
|
2160
|
+
if (!authWindow) {
|
|
2161
|
+
throw new Error(`Failed to open ${oauth.capitalizeProviderName(provider)} login window.`);
|
|
2162
|
+
}
|
|
2163
|
+
authWindow.location.href = authUrl;
|
|
2164
|
+
return new Promise((resolve, reject) => {
|
|
2165
|
+
const interval = setInterval(() => {
|
|
2166
|
+
try {
|
|
2432
2167
|
if (authWindow.closed) {
|
|
2433
2168
|
clearInterval(interval);
|
|
2169
|
+
reject(new Error("Authentication window was closed."));
|
|
2170
|
+
return;
|
|
2434
2171
|
}
|
|
2435
|
-
|
|
2172
|
+
const url = authWindow.location.href || "";
|
|
2173
|
+
if (url.startsWith(window.location.origin)) {
|
|
2174
|
+
const result = oauth.parseOAuthResponse(url, provider);
|
|
2175
|
+
if (result) {
|
|
2176
|
+
authWindow.close();
|
|
2177
|
+
clearInterval(interval);
|
|
2178
|
+
oauth.completeOAuthPopup({
|
|
2179
|
+
provider,
|
|
2180
|
+
publicKey,
|
|
2181
|
+
result,
|
|
2182
|
+
callbacks,
|
|
2183
|
+
completeOauth,
|
|
2184
|
+
onOauthSuccess: params?.onOauthSuccess,
|
|
2185
|
+
exchangeCodeForToken: async codeVerifier => {
|
|
2186
|
+
const tokenData = await oauth.exchangeFacebookCodeForToken(clientId, redirectUri, result.authCode, codeVerifier);
|
|
2187
|
+
return tokenData.id_token;
|
|
2188
|
+
}
|
|
2189
|
+
}).then(() => resolve()).catch(reject);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
} catch {
|
|
2193
|
+
// Ignore cross-origin errors
|
|
2194
|
+
}
|
|
2195
|
+
}, 500);
|
|
2196
|
+
if (authWindow.closed) {
|
|
2197
|
+
clearInterval(interval);
|
|
2436
2198
|
}
|
|
2437
|
-
}
|
|
2438
|
-
throw error;
|
|
2439
|
-
}
|
|
2199
|
+
});
|
|
2440
2200
|
}, [callbacks, completeOauth, createApiKeyPair, masterConfig]);
|
|
2441
2201
|
const handleLogin = react.useCallback(async params => {
|
|
2442
2202
|
const logo = masterConfig?.ui?.darkMode ? params?.logoDark ?? masterConfig?.ui?.logoDark : params?.logoLight ?? masterConfig?.ui?.logoLight;
|
|
@@ -2596,6 +2356,7 @@ const ClientProvider = ({
|
|
|
2596
2356
|
successPageDuration = 2000,
|
|
2597
2357
|
stampWith,
|
|
2598
2358
|
keyName,
|
|
2359
|
+
keyFormat,
|
|
2599
2360
|
clearClipboardOnPaste,
|
|
2600
2361
|
organizationId
|
|
2601
2362
|
} = params || {};
|
|
@@ -2606,6 +2367,7 @@ const ClientProvider = ({
|
|
|
2606
2367
|
importType: base.ImportType.PrivateKey,
|
|
2607
2368
|
curve: curve,
|
|
2608
2369
|
addressFormats: addressFormats,
|
|
2370
|
+
keyFormat: keyFormat,
|
|
2609
2371
|
onError: error => {
|
|
2610
2372
|
reject(error);
|
|
2611
2373
|
},
|
|
@@ -3337,7 +3099,8 @@ const ClientProvider = ({
|
|
|
3337
3099
|
const {
|
|
3338
3100
|
providerName,
|
|
3339
3101
|
stampWith,
|
|
3340
|
-
successPageDuration = 2000
|
|
3102
|
+
successPageDuration = 2000,
|
|
3103
|
+
openInPage = masterConfig?.auth?.oauthConfig?.openOauthInPage ?? false
|
|
3341
3104
|
} = params;
|
|
3342
3105
|
const s = await getSession();
|
|
3343
3106
|
const organizationId = params?.organizationId || s?.organizationId;
|
|
@@ -3372,40 +3135,81 @@ const ClientProvider = ({
|
|
|
3372
3135
|
showTitle: false
|
|
3373
3136
|
});
|
|
3374
3137
|
};
|
|
3138
|
+
if (openInPage) {
|
|
3139
|
+
oauth.storeOAuthAddProviderMetadata({
|
|
3140
|
+
organizationId,
|
|
3141
|
+
userId,
|
|
3142
|
+
...(stampWith && {
|
|
3143
|
+
stampWith: stampWith
|
|
3144
|
+
}),
|
|
3145
|
+
successPageDuration
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
const additionalState = openInPage ? {
|
|
3149
|
+
oauthIntent: oauth.OAUTH_INTENT_ADD_PROVIDER,
|
|
3150
|
+
...(successPageDuration > 0 && {
|
|
3151
|
+
openModal: "true"
|
|
3152
|
+
})
|
|
3153
|
+
} : undefined;
|
|
3375
3154
|
switch (providerName) {
|
|
3376
3155
|
case sdkTypes.OAuthProviders.DISCORD:
|
|
3377
3156
|
{
|
|
3378
3157
|
return await handleDiscordOauth({
|
|
3379
|
-
openInPage
|
|
3380
|
-
|
|
3158
|
+
openInPage,
|
|
3159
|
+
...(additionalState && {
|
|
3160
|
+
additionalState
|
|
3161
|
+
}),
|
|
3162
|
+
...(!openInPage && {
|
|
3163
|
+
onOauthSuccess
|
|
3164
|
+
})
|
|
3381
3165
|
});
|
|
3382
3166
|
}
|
|
3383
3167
|
case sdkTypes.OAuthProviders.X:
|
|
3384
3168
|
{
|
|
3385
3169
|
return await handleXOauth({
|
|
3386
|
-
openInPage
|
|
3387
|
-
|
|
3170
|
+
openInPage,
|
|
3171
|
+
...(additionalState && {
|
|
3172
|
+
additionalState
|
|
3173
|
+
}),
|
|
3174
|
+
...(!openInPage && {
|
|
3175
|
+
onOauthSuccess
|
|
3176
|
+
})
|
|
3388
3177
|
});
|
|
3389
3178
|
}
|
|
3390
3179
|
case sdkTypes.OAuthProviders.GOOGLE:
|
|
3391
3180
|
{
|
|
3392
3181
|
return await handleGoogleOauth({
|
|
3393
|
-
openInPage
|
|
3394
|
-
|
|
3182
|
+
openInPage,
|
|
3183
|
+
...(additionalState && {
|
|
3184
|
+
additionalState
|
|
3185
|
+
}),
|
|
3186
|
+
...(!openInPage && {
|
|
3187
|
+
onOauthSuccess
|
|
3188
|
+
})
|
|
3395
3189
|
});
|
|
3396
3190
|
}
|
|
3397
3191
|
case sdkTypes.OAuthProviders.APPLE:
|
|
3398
3192
|
{
|
|
3399
3193
|
return await handleAppleOauth({
|
|
3400
|
-
openInPage
|
|
3401
|
-
|
|
3194
|
+
openInPage,
|
|
3195
|
+
...(additionalState && {
|
|
3196
|
+
additionalState
|
|
3197
|
+
}),
|
|
3198
|
+
...(!openInPage && {
|
|
3199
|
+
onOauthSuccess
|
|
3200
|
+
})
|
|
3402
3201
|
});
|
|
3403
3202
|
}
|
|
3404
3203
|
case sdkTypes.OAuthProviders.FACEBOOK:
|
|
3405
3204
|
{
|
|
3406
3205
|
return await handleFacebookOauth({
|
|
3407
|
-
openInPage
|
|
3408
|
-
|
|
3206
|
+
openInPage,
|
|
3207
|
+
...(additionalState && {
|
|
3208
|
+
additionalState
|
|
3209
|
+
}),
|
|
3210
|
+
...(!openInPage && {
|
|
3211
|
+
onOauthSuccess
|
|
3212
|
+
})
|
|
3409
3213
|
});
|
|
3410
3214
|
}
|
|
3411
3215
|
default:
|