@supabase/gotrue-js 2.16.0 → 2.18.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.
@@ -23,9 +23,12 @@ import {
23
23
  uuid,
24
24
  retryable,
25
25
  sleep,
26
+ generatePKCEVerifier,
27
+ generatePKCEChallenge,
26
28
  } from './lib/helpers'
27
29
  import localStorageAdapter from './lib/local-storage'
28
30
  import { polyfillGlobalThis } from './lib/polyfills'
31
+
29
32
  import type {
30
33
  AuthChangeEvent,
31
34
  AuthResponse,
@@ -63,6 +66,7 @@ import type {
63
66
  AuthenticatorAssuranceLevels,
64
67
  Factor,
65
68
  MFAChallengeAndVerifyParams,
69
+ OAuthFlowType,
66
70
  } from './lib/types'
67
71
 
68
72
  polyfillGlobalThis() // Make "globalThis" available
@@ -220,9 +224,10 @@ export default class GoTrueClient {
220
224
  await this._saveSession(session)
221
225
 
222
226
  setTimeout(() => {
223
- this._notifyAllSubscribers('SIGNED_IN', session)
224
227
  if (redirectType === 'recovery') {
225
228
  this._notifyAllSubscribers('PASSWORD_RECOVERY', session)
229
+ } else {
230
+ this._notifyAllSubscribers('SIGNED_IN', session)
226
231
  }
227
232
  }, 0)
228
233
 
@@ -378,14 +383,43 @@ export default class GoTrueClient {
378
383
  */
379
384
  async signInWithOAuth(credentials: SignInWithOAuthCredentials): Promise<OAuthResponse> {
380
385
  await this._removeSession()
381
- return this._handleProviderSignIn(credentials.provider, {
386
+
387
+ return await this._handleProviderSignIn(credentials.provider, {
382
388
  redirectTo: credentials.options?.redirectTo,
383
389
  scopes: credentials.options?.scopes,
384
390
  queryParams: credentials.options?.queryParams,
385
391
  skipBrowserRedirect: credentials.options?.skipBrowserRedirect,
392
+ flowType: credentials.options?.flowType ?? 'implicit',
386
393
  })
387
394
  }
388
395
 
396
+ /**
397
+ * Log in an existing user via a third-party provider.
398
+ */
399
+ async exchangeCodeForSession(authCode: string): Promise<AuthResponse> {
400
+ const codeVerifier = await getItemAsync(this.storage, `${this.storageKey}-oauth-code-verifier`)
401
+ const { data, error } = await _request(
402
+ this.fetch,
403
+ 'POST',
404
+ `${this.url}/token?grant_type=oauth_pkce`,
405
+ {
406
+ headers: this.headers,
407
+ body: {
408
+ auth_code: authCode,
409
+ code_verifier: codeVerifier,
410
+ },
411
+ xform: _sessionResponse,
412
+ }
413
+ )
414
+ await removeItemAsync(this.storage, `${this.storageKey}-oauth-code-verifier`)
415
+ if (error || !data) return { data: { user: null, session: null }, error }
416
+ if (data.session) {
417
+ await this._saveSession(data.session)
418
+ this._notifyAllSubscribers('SIGNED_IN', data.session)
419
+ }
420
+ return { data, error }
421
+ }
422
+
389
423
  /**
390
424
  * Allows signing in with an ID token issued by certain supported providers.
391
425
  * The ID token is verified for validity and a new session is established.
@@ -1040,24 +1074,27 @@ export default class GoTrueClient {
1040
1074
  return isValidSession
1041
1075
  }
1042
1076
 
1043
- private _handleProviderSignIn(
1077
+ private async _handleProviderSignIn(
1044
1078
  provider: Provider,
1045
1079
  options: {
1046
1080
  redirectTo?: string
1047
1081
  scopes?: string
1048
1082
  queryParams?: { [key: string]: string }
1049
1083
  skipBrowserRedirect?: boolean
1050
- } = {}
1084
+ flowType: OAuthFlowType
1085
+ }
1051
1086
  ) {
1052
- const url: string = this._getUrlForProvider(provider, {
1087
+ const url: string = await this._getUrlForProvider(provider, {
1053
1088
  redirectTo: options.redirectTo,
1054
1089
  scopes: options.scopes,
1055
1090
  queryParams: options.queryParams,
1091
+ flowType: options.flowType,
1056
1092
  })
1057
1093
  // try to open on the browser
1058
1094
  if (isBrowser() && !options.skipBrowserRedirect) {
1059
1095
  window.location.assign(url)
1060
1096
  }
1097
+
1061
1098
  return { data: { provider, url }, error: null }
1062
1099
  }
1063
1100
 
@@ -1355,13 +1392,15 @@ export default class GoTrueClient {
1355
1392
  * @param options.redirectTo A URL or mobile address to send the user to after they are confirmed.
1356
1393
  * @param options.scopes A space-separated list of scopes granted to the OAuth application.
1357
1394
  * @param options.queryParams An object of key-value pairs containing query parameters granted to the OAuth application.
1395
+ * @param options.flowType OAuth flow to use - defaults to implicit flow. PKCE is recommended for mobile and server-side applications.
1358
1396
  */
1359
- private _getUrlForProvider(
1397
+ private async _getUrlForProvider(
1360
1398
  provider: Provider,
1361
1399
  options: {
1362
1400
  redirectTo?: string
1363
1401
  scopes?: string
1364
1402
  queryParams?: { [key: string]: string }
1403
+ flowType: OAuthFlowType
1365
1404
  }
1366
1405
  ) {
1367
1406
  const urlParams: string[] = [`provider=${encodeURIComponent(provider)}`]
@@ -1371,10 +1410,22 @@ export default class GoTrueClient {
1371
1410
  if (options?.scopes) {
1372
1411
  urlParams.push(`scopes=${encodeURIComponent(options.scopes)}`)
1373
1412
  }
1413
+ if (options?.flowType === 'pkce') {
1414
+ const codeVerifier = generatePKCEVerifier()
1415
+ await setItemAsync(this.storage, `${this.storageKey}-oauth-code-verifier`, codeVerifier)
1416
+ const codeChallenge = await generatePKCEChallenge(codeVerifier)
1417
+ const flowParams = new URLSearchParams({
1418
+ flow_type: `${encodeURIComponent(options.flowType)}`,
1419
+ code_challenge: `${encodeURIComponent(codeChallenge)}`,
1420
+ code_challenge_method: `${encodeURIComponent('s256')}`,
1421
+ })
1422
+ urlParams.push(flowParams.toString())
1423
+ }
1374
1424
  if (options?.queryParams) {
1375
1425
  const query = new URLSearchParams(options.queryParams)
1376
1426
  urlParams.push(query.toString())
1377
1427
  }
1428
+
1378
1429
  return `${this.url}/authorize?${urlParams.join('&')}`
1379
1430
  }
1380
1431
 
@@ -1,5 +1,5 @@
1
1
  import { SupportedStorage } from './types'
2
-
2
+ import sha256CryptoJS from 'crypto-js/sha256'
3
3
  export function expiresAt(expiresIn: number) {
4
4
  const timeNow = Math.round(Date.now() / 1000)
5
5
  return timeNow + expiresIn
@@ -236,3 +236,44 @@ export function retryable<T>(
236
236
 
237
237
  return promise
238
238
  }
239
+
240
+ function dec2hex(dec: number) {
241
+ return ('0' + dec.toString(16)).substr(-2)
242
+ }
243
+
244
+ // Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs
245
+ export function generatePKCEVerifier() {
246
+ const verifierLength = 56
247
+ const array = new Uint32Array(verifierLength)
248
+ if (!isBrowser()) {
249
+ for (let i = 0; i < verifierLength; i++) {
250
+ array[i] = Math.floor(Math.random() * 256)
251
+ }
252
+ } else {
253
+ window.crypto.getRandomValues(array)
254
+ }
255
+ return Array.from(array, dec2hex).join('')
256
+ }
257
+
258
+ async function sha256(randomString: string) {
259
+ const encoder = new TextEncoder()
260
+ const encodedData = encoder.encode(randomString)
261
+ if (!isBrowser()) {
262
+ return sha256CryptoJS(randomString).toString()
263
+ }
264
+ const hash = await window.crypto.subtle.digest('SHA-256', encodedData)
265
+ const bytes = new Uint8Array(hash)
266
+
267
+ return Array.from(bytes)
268
+ .map((c) => String.fromCharCode(c))
269
+ .join('')
270
+ }
271
+
272
+ function base64urlencode(str: string) {
273
+ return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
274
+ }
275
+
276
+ export async function generatePKCEChallenge(verifier: string) {
277
+ const hashed = await sha256(verifier)
278
+ return base64urlencode(hashed)
279
+ }
package/src/lib/types.ts CHANGED
@@ -429,6 +429,7 @@ export type SignInWithPasswordlessCredentials =
429
429
  }
430
430
  }
431
431
 
432
+ export type OAuthFlowType = 'implicit' | 'pkce'
432
433
  export type SignInWithOAuthCredentials = {
433
434
  /** One of the providers supported by GoTrue. */
434
435
  provider: Provider
@@ -441,6 +442,8 @@ export type SignInWithOAuthCredentials = {
441
442
  queryParams?: { [key: string]: string }
442
443
  /** If set to true does not immediately redirect the current browser context to visit the OAuth authorization page for the provider. */
443
444
  skipBrowserRedirect?: boolean
445
+ /** If set to 'pkce' PKCE flow. Defaults to the 'implicit' flow otherwise */
446
+ flowType?: OAuthFlowType
444
447
  }
445
448
  }
446
449
 
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '2.16.0'
2
+ export const version = '2.18.0'