@turnkey/react-wallet-kit 1.7.2 → 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.
Files changed (33) hide show
  1. package/dist/components/import/Import.d.ts +2 -0
  2. package/dist/components/import/Import.d.ts.map +1 -1
  3. package/dist/components/import/Import.js +3 -2
  4. package/dist/components/import/Import.js.map +1 -1
  5. package/dist/components/import/Import.mjs +4 -3
  6. package/dist/components/import/Import.mjs.map +1 -1
  7. package/dist/providers/client/Provider.d.ts +1 -1
  8. package/dist/providers/client/Provider.d.ts.map +1 -1
  9. package/dist/providers/client/Provider.js +684 -880
  10. package/dist/providers/client/Provider.js.map +1 -1
  11. package/dist/providers/client/Provider.mjs +2329 -2494
  12. package/dist/providers/client/Provider.mjs.map +1 -1
  13. package/dist/providers/client/Types.d.ts +4 -0
  14. package/dist/providers/client/Types.d.ts.map +1 -1
  15. package/dist/providers/client/Types.js.map +1 -1
  16. package/dist/providers/client/Types.mjs.map +1 -1
  17. package/dist/tests/oauth-test.d.ts +2 -0
  18. package/dist/tests/oauth-test.d.ts.map +1 -0
  19. package/dist/types/method-types.d.ts +2 -0
  20. package/dist/types/method-types.d.ts.map +1 -1
  21. package/dist/utils/oauth.d.ts +323 -0
  22. package/dist/utils/oauth.d.ts.map +1 -0
  23. package/dist/utils/oauth.js +613 -0
  24. package/dist/utils/oauth.js.map +1 -0
  25. package/dist/utils/oauth.mjs +670 -0
  26. package/dist/utils/oauth.mjs.map +1 -0
  27. package/dist/utils/utils.d.ts +1 -41
  28. package/dist/utils/utils.d.ts.map +1 -1
  29. package/dist/utils/utils.js +0 -161
  30. package/dist/utils/utils.js.map +1 -1
  31. package/dist/utils/utils.mjs +2 -216
  32. package/dist/utils/utils.mjs.map +1 -1
  33. 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 || window.location.search) {
93
- // Handle Facebook redirect (uses search params with code)
94
- if (window.location.search && window.location.search.includes("code=") && window.location.search.includes("state=")) {
95
- const searchParams = new URLSearchParams(window.location.search.substring(1));
96
- const code = searchParams.get("code");
97
- const state = searchParams.get("state");
98
- // Parse state parameter
99
- if (state && code) {
100
- const stateParams = new URLSearchParams(state);
101
- const provider = stateParams.get("provider");
102
- const flow = stateParams.get("flow");
103
- const publicKey = stateParams.get("publicKey");
104
- const openModal = stateParams.get("openModal");
105
- const sessionKey = stateParams.get("sessionKey");
106
- if (provider === "facebook" && flow === "redirect" && publicKey) {
107
- // We have all the required parameters for a Facebook PKCE flow
108
- const clientId = masterConfig?.auth?.oauthConfig?.facebookClientId;
109
- const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
110
- if (clientId && redirectURI) {
111
- await utils.handleFacebookPKCEFlow({
112
- code,
113
- publicKey,
114
- openModal,
115
- clientId,
116
- redirectURI,
117
- callbacks,
118
- completeOauth,
119
- onPushPage: oidcToken => {
120
- return new Promise((resolve, reject) => {
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: `Facebook OAuth`,
123
- content: jsxRuntime.jsx(Action.ActionPage, {
124
- title: `Authenticating with Facebook...`,
125
- action: async () => {
126
- try {
127
- await completeOauth({
128
- oidcToken,
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
- showTitle: false,
148
- onClose: () => {
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
- }).catch(error => {
155
- // Handle errors
156
- if (callbacks?.onError) {
157
- callbacks.onError(error instanceof sdkTypes.TurnkeyError ? error : new sdkTypes.TurnkeyError("Facebook authentication failed", sdkTypes.TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, error));
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
- if (provider === "discord" && flow === "redirect" && publicKey) {
163
- const clientId = masterConfig?.auth?.oauthConfig?.discordClientId;
164
- const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
165
- const verifier = sessionStorage.getItem("discord_verifier");
166
- const nonce = stateParams.get("nonce");
167
- const sessionKey = stateParams.get("sessionKey");
168
- if (clientId && redirectURI && verifier && nonce) {
169
- await new Promise((resolve, reject) => {
170
- pushPage({
171
- key: `Discord OAuth`,
172
- content: jsxRuntime.jsx(Action.ActionPage, {
173
- title: `Authenticating with Discord...`,
174
- action: async () => {
175
- try {
176
- const resp = await client?.httpClient.proxyOAuth2Authenticate({
177
- provider: "OAUTH2_PROVIDER_DISCORD",
178
- authCode: code,
179
- redirectUri: redirectURI,
180
- codeVerifier: verifier,
181
- clientId,
182
- nonce: nonce
183
- });
184
- sessionStorage.removeItem("discord_verifier");
185
- const oidcToken = resp?.oidcToken;
186
- if (!oidcToken) {
187
- throw new sdkTypes.TurnkeyError("Missing OIDC token", sdkTypes.TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
188
- }
189
- await completeOauth({
190
- oidcToken,
191
- publicKey,
192
- providerName: "discord",
193
- ...(sessionKey && {
194
- sessionKey
195
- })
196
- });
197
- // Clean up the URL after processing
198
- window.history.replaceState(null, document.title, window.location.pathname);
199
- resolve(null);
200
- } catch (err) {
201
- reject(err);
202
- if (callbacks?.onError) {
203
- callbacks.onError(err instanceof sdkTypes.TurnkeyError ? err : new sdkTypes.TurnkeyError("Discord authentication failed", sdkTypes.TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, err));
204
- }
205
- }
206
- },
207
- icon: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, {
208
- size: "3x",
209
- icon: freeBrandsSvgIcons.faDiscord
210
- })
211
- }),
212
- showTitle: false,
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
- if (provider === "twitter" && flow === "redirect" && publicKey) {
221
- const clientId = masterConfig?.auth?.oauthConfig?.xClientId;
222
- const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri;
223
- const verifier = sessionStorage.getItem("twitter_verifier");
224
- const nonce = stateParams.get("nonce");
225
- const sessionKey = stateParams.get("sessionKey");
226
- if (clientId && redirectURI && verifier && nonce) {
227
- await new Promise((resolve, reject) => {
228
- pushPage({
229
- key: `Twitter OAuth`,
230
- content: jsxRuntime.jsx(Action.ActionPage, {
231
- title: `Authenticating with Twitter...`,
232
- action: async () => {
233
- try {
234
- const resp = await client?.httpClient.proxyOAuth2Authenticate({
235
- provider: "OAUTH2_PROVIDER_X",
236
- authCode: code,
237
- redirectUri: redirectURI,
238
- codeVerifier: verifier,
239
- clientId,
240
- nonce: nonce
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
- // Handle Google/Apple redirects (uses hash with id_token)
281
- else if (window.location.hash) {
282
- const hash = window.location.hash.substring(1);
283
- // Parse the hash using our helper functions
284
- const {
285
- idToken,
286
- provider,
287
- flow,
288
- publicKey,
289
- openModal,
290
- sessionKey
291
- } = utils.parseOAuthRedirect(hash);
292
- if (idToken && flow === "redirect" && publicKey) {
293
- if (openModal === "true") {
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
- } else {
346
- completeOauth({
347
- oidcToken: idToken,
348
- publicKey,
349
- ...(provider ? {
350
- providerName: provider
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
- // Clean up the URL after processing
355
- window.history.replaceState(null, document.title, window.location.pathname + window.location.search);
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
- try {
1771
- if (!masterConfig) {
1772
- throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1773
- }
1774
- if (!clientId) {
1775
- throw new sdkTypes.TurnkeyError("Discord Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1776
- }
1777
- if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
1778
- throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1779
- }
1780
- const flow = openInPage ? "redirect" : "popup";
1781
- const redirectURI = masterConfig.auth?.oauthConfig.oauthRedirectUri;
1782
- // Create key pair and generate nonce
1783
- const publicKey = await createApiKeyPair();
1784
- if (!publicKey) {
1785
- throw new Error("Failed to create public key for OAuth.");
1786
- }
1787
- const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
1788
- // Generate PKCE challenge pair
1789
- const {
1790
- verifier,
1791
- codeChallenge
1792
- } = await utils.generateChallengePair();
1793
- sessionStorage.setItem("discord_verifier", verifier);
1794
- // Construct Discord Auth URL
1795
- const discordAuthUrl = new URL(utils.DISCORD_AUTH_URL);
1796
- discordAuthUrl.searchParams.set("client_id", clientId);
1797
- discordAuthUrl.searchParams.set("redirect_uri", redirectURI);
1798
- discordAuthUrl.searchParams.set("response_type", "code");
1799
- discordAuthUrl.searchParams.set("code_challenge", codeChallenge);
1800
- discordAuthUrl.searchParams.set("code_challenge_method", "S256");
1801
- discordAuthUrl.searchParams.set("scope", "identify email");
1802
- discordAuthUrl.searchParams.set("state", `provider=discord&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}&nonce=${nonce}`);
1803
- if (additionalParameters) {
1804
- const extra = Object.entries(additionalParameters).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
1805
- if (extra) {
1806
- discordAuthUrl.searchParams.set("state", discordAuthUrl.searchParams.get("state") + `&${extra}`);
1807
- }
1808
- }
1809
- if (openInPage) {
1810
- window.location.href = discordAuthUrl.toString();
1811
- return new Promise((_, reject) => {
1812
- const timeout = setTimeout(() => {
1813
- reject(new Error("Authentication timed out."));
1814
- }, 300000);
1815
- window.addEventListener("beforeunload", () => clearTimeout(timeout));
1816
- });
1817
- } else {
1818
- const width = utils.popupWidth;
1819
- const height = utils.popupHeight;
1820
- const left = window.screenX + (window.innerWidth - width) / 2;
1821
- const top = window.screenY + (window.innerHeight - height) / 2;
1822
- const authWindow = window.open("about:blank", "_blank", `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`);
1823
- if (!authWindow) {
1824
- throw new Error("Failed to open Discord login window.");
1825
- }
1826
- authWindow.location.href = discordAuthUrl.toString();
1827
- return new Promise((resolve, reject) => {
1828
- const interval = setInterval(() => {
1829
- try {
1830
- if (authWindow.closed) {
1831
- clearInterval(interval);
1832
- reject(new Error("Authentication window was closed."));
1833
- return;
1834
- }
1835
- const url = authWindow.location.href || "";
1836
- if (url.startsWith(window.location.origin)) {
1837
- const urlParams = new URLSearchParams(new URL(url).search);
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: redirectURI,
1853
- codeVerifier: verifier,
1842
+ authCode: result.authCode,
1843
+ redirectUri,
1844
+ codeVerifier,
1854
1845
  clientId,
1855
- nonce: nonce
1856
- }).then(resp => {
1857
- sessionStorage.removeItem("discord_verifier");
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
- } catch (error) {
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
- try {
1911
- if (!masterConfig) {
1912
- throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1913
- }
1914
- if (!clientId) {
1915
- throw new sdkTypes.TurnkeyError("Twitter Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1916
- }
1917
- if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
1918
- throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
1919
- }
1920
- const flow = openInPage ? "redirect" : "popup";
1921
- const redirectURI = masterConfig.auth?.oauthConfig.oauthRedirectUri;
1922
- // Create key pair and generate nonce
1923
- const publicKey = await createApiKeyPair();
1924
- if (!publicKey) {
1925
- throw new Error("Failed to create public key for OAuth.");
1926
- }
1927
- const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
1928
- // Generate PKCE challenge pair
1929
- const {
1930
- verifier,
1931
- codeChallenge
1932
- } = await utils.generateChallengePair();
1933
- sessionStorage.setItem("twitter_verifier", verifier);
1934
- // Construct Twitter Auth URL
1935
- const twitterAuthUrl = new URL(utils.X_AUTH_URL);
1936
- twitterAuthUrl.searchParams.set("client_id", clientId);
1937
- twitterAuthUrl.searchParams.set("redirect_uri", redirectURI);
1938
- twitterAuthUrl.searchParams.set("response_type", "code");
1939
- twitterAuthUrl.searchParams.set("code_challenge", codeChallenge);
1940
- twitterAuthUrl.searchParams.set("code_challenge_method", "S256");
1941
- twitterAuthUrl.searchParams.set("scope", "tweet.read users.read");
1942
- twitterAuthUrl.searchParams.set("state", `provider=twitter&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}&nonce=${nonce}`);
1943
- if (additionalParameters) {
1944
- const extra = Object.entries(additionalParameters).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
1945
- if (extra) {
1946
- twitterAuthUrl.searchParams.set("state", twitterAuthUrl.searchParams.get("state") + `&${extra}`);
1947
- }
1948
- }
1949
- if (openInPage) {
1950
- window.location.href = twitterAuthUrl.toString();
1951
- return new Promise((_, reject) => {
1952
- const timeout = setTimeout(() => {
1953
- reject(new Error("Authentication timed out."));
1954
- }, 300000);
1955
- window.addEventListener("beforeunload", () => clearTimeout(timeout));
1956
- });
1957
- } else {
1958
- const width = utils.popupWidth;
1959
- const height = utils.popupHeight;
1960
- const left = window.screenX + (window.innerWidth - width) / 2;
1961
- const top = window.screenY + (window.innerHeight - height) / 2;
1962
- const authWindow = window.open("about:blank", "_blank", `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes`);
1963
- if (!authWindow) {
1964
- throw new Error("Failed to open Twitter login window.");
1965
- }
1966
- authWindow.location.href = twitterAuthUrl.toString();
1967
- return new Promise((resolve, reject) => {
1968
- const interval = setInterval(() => {
1969
- try {
1970
- if (authWindow.closed) {
1971
- clearInterval(interval);
1972
- reject(new Error("Authentication window was closed."));
1973
- return;
1974
- }
1975
- const url = authWindow.location.href || "";
1976
- if (url.startsWith(window.location.origin)) {
1977
- const urlParams = new URLSearchParams(new URL(url).search);
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: redirectURI,
1993
- codeVerifier: verifier,
1937
+ authCode: result.authCode,
1938
+ redirectUri,
1939
+ codeVerifier,
1994
1940
  clientId,
1995
1941
  nonce
1996
- }).then(resp => {
1997
- sessionStorage.removeItem("twitter_verifier");
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
- } catch (error) {
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
- try {
2051
- if (!masterConfig) {
2052
- throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2053
- }
2054
- if (!clientId) {
2055
- throw new sdkTypes.TurnkeyError("Google Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2056
- }
2057
- if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
2058
- throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2059
- }
2060
- const flow = openInPage ? "redirect" : "popup";
2061
- const redirectURI = masterConfig.auth?.oauthConfig.oauthRedirectUri.replace(/\/$/, "");
2062
- // Create key pair and generate nonce
2063
- const publicKey = await createApiKeyPair();
2064
- if (!publicKey) {
2065
- throw new Error("Failed to create public key for OAuth.");
2066
- }
2067
- const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
2068
- // Construct Google Auth URL
2069
- const googleAuthUrl = new URL(utils.GOOGLE_AUTH_URL);
2070
- googleAuthUrl.searchParams.set("client_id", clientId);
2071
- googleAuthUrl.searchParams.set("redirect_uri", redirectURI);
2072
- googleAuthUrl.searchParams.set("response_type", "id_token");
2073
- googleAuthUrl.searchParams.set("scope", "openid email profile");
2074
- googleAuthUrl.searchParams.set("nonce", nonce);
2075
- googleAuthUrl.searchParams.set("prompt", "select_account");
2076
- // Create state parameter
2077
- let state = `provider=google&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}`;
2078
- if (additionalParameters) {
2079
- const additionalState = Object.entries(additionalParameters).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
2080
- if (additionalState) {
2081
- state += `&${additionalState}`;
2082
- }
2083
- }
2084
- googleAuthUrl.searchParams.set("state", state);
2085
- if (openInPage) {
2086
- // Redirect current page to Google Auth
2087
- window.location.href = googleAuthUrl.toString();
2088
- return new Promise((_, reject) => {
2089
- // By here, the page should have already redirected. We wait here since the function is async.
2090
- // We want any function that runs this to simply wait until the page redirects.
2091
- // A 5 min timeout is set just in case, idk
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
- } catch (error) {
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
- try {
2180
- if (!masterConfig) {
2181
- throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2182
- }
2183
- if (!clientId) {
2184
- throw new sdkTypes.TurnkeyError("Apple Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2185
- }
2186
- if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
2187
- throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2188
- }
2189
- const flow = openInPage ? "redirect" : "popup";
2190
- const redirectURI = masterConfig.auth?.oauthConfig.oauthRedirectUri; // TODO (Amir): Apple needs the '/' at the end. Maybe we should add it if not there?
2191
- // Create key pair and generate nonce
2192
- const publicKey = await createApiKeyPair();
2193
- if (!publicKey) {
2194
- throw new Error("Failed to create public key for OAuth.");
2195
- }
2196
- const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
2197
- // Construct Apple Auth URL
2198
- const appleAuthUrl = new URL(utils.APPLE_AUTH_URL);
2199
- appleAuthUrl.searchParams.set("client_id", clientId);
2200
- appleAuthUrl.searchParams.set("redirect_uri", redirectURI);
2201
- appleAuthUrl.searchParams.set("response_type", "code id_token");
2202
- appleAuthUrl.searchParams.set("response_mode", "fragment");
2203
- appleAuthUrl.searchParams.set("nonce", nonce);
2204
- // Create state parameter
2205
- let state = `provider=apple&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}`;
2206
- if (additionalParameters) {
2207
- const additionalState = Object.entries(additionalParameters).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
2208
- if (additionalState) {
2209
- state += `&${additionalState}`;
2210
- }
2211
- }
2212
- appleAuthUrl.searchParams.set("state", state);
2213
- if (openInPage) {
2214
- // Redirect current page to Apple Auth
2215
- window.location.href = appleAuthUrl.toString();
2216
- return new Promise((_, reject) => {
2217
- // Set a timeout just in case the redirect doesn't happen
2218
- const timeout = setTimeout(() => {
2219
- reject(new Error("Authentication timed out."));
2220
- }, 300000); // 5 minutes
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
- } catch (error) {
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
- try {
2305
- if (!masterConfig) {
2306
- throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2307
- }
2308
- if (!clientId) {
2309
- throw new sdkTypes.TurnkeyError("Facebook Client ID is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2310
- }
2311
- if (!masterConfig.auth?.oauthConfig?.oauthRedirectUri) {
2312
- throw new sdkTypes.TurnkeyError("OAuth Redirect URI is not configured.", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION);
2313
- }
2314
- const flow = openInPage ? "redirect" : "popup";
2315
- const redirectURI = masterConfig.auth?.oauthConfig.oauthRedirectUri;
2316
- // Create key pair and generate nonce
2317
- const publicKey = await createApiKeyPair();
2318
- if (!publicKey) {
2319
- throw new Error("Failed to create public key for OAuth.");
2320
- }
2321
- const nonce = utils$1.bytesToHex(sha2.sha256(publicKey));
2322
- // Generate PKCE challenge pair
2323
- const {
2324
- verifier,
2325
- codeChallenge
2326
- } = await utils.generateChallengePair();
2327
- // Store verifier for later token exchange
2328
- sessionStorage.setItem("facebook_verifier", verifier);
2329
- // Construct Facebook Auth URL
2330
- const facebookAuthUrl = new URL(utils.FACEBOOK_AUTH_URL);
2331
- facebookAuthUrl.searchParams.set("client_id", clientId);
2332
- facebookAuthUrl.searchParams.set("redirect_uri", redirectURI);
2333
- facebookAuthUrl.searchParams.set("response_type", "code");
2334
- facebookAuthUrl.searchParams.set("code_challenge", codeChallenge);
2335
- facebookAuthUrl.searchParams.set("code_challenge_method", "S256");
2336
- facebookAuthUrl.searchParams.set("nonce", nonce);
2337
- facebookAuthUrl.searchParams.set("scope", "openid");
2338
- // Create state parameter
2339
- let state = `provider=facebook&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}`;
2340
- if (additionalParameters) {
2341
- const additionalState = Object.entries(additionalParameters).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
2342
- if (additionalState) {
2343
- state += `&${additionalState}`;
2344
- }
2345
- }
2346
- facebookAuthUrl.searchParams.set("state", state);
2347
- if (openInPage) {
2348
- // Redirect current page to Facebook Auth
2349
- window.location.href = facebookAuthUrl.toString();
2350
- return new Promise((_, reject) => {
2351
- // Set a timeout just in case the redirect doesn't happen
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
- } catch (error) {
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: false,
3380
- onOauthSuccess
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: false,
3387
- onOauthSuccess
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: false,
3394
- onOauthSuccess
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: false,
3401
- onOauthSuccess
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: false,
3408
- onOauthSuccess
3206
+ openInPage,
3207
+ ...(additionalState && {
3208
+ additionalState
3209
+ }),
3210
+ ...(!openInPage && {
3211
+ onOauthSuccess
3212
+ })
3409
3213
  });
3410
3214
  }
3411
3215
  default: