@supabase/gotrue-js 2.69.1 → 2.70.0-rc.2

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.
@@ -106,8 +106,11 @@ import type {
106
106
  JWK,
107
107
  JwtPayload,
108
108
  JwtHeader,
109
+ SolanaWeb3Credentials,
110
+ SolanaWallet,
111
+ Web3Credentials,
109
112
  } from './lib/types'
110
- import { stringToUint8Array } from './lib/base64url'
113
+ import { stringToUint8Array, bytesToBase64URL } from './lib/base64url'
111
114
 
112
115
  polyfillGlobalThis() // Make "globalThis" available
113
116
 
@@ -601,6 +604,214 @@ export default class GoTrueClient {
601
604
  })
602
605
  }
603
606
 
607
+ /**
608
+ * Signs in a user by verifying a message signed by the user's private key.
609
+ * Only Solana supported at this time, using the Sign in with Solana standard.
610
+ */
611
+ async signInWithWeb3(credentials: Web3Credentials): Promise<
612
+ | {
613
+ data: { session: Session; user: User }
614
+ error: null
615
+ }
616
+ | { data: { session: null; user: null }; error: AuthError }
617
+ > {
618
+ const { chain } = credentials
619
+
620
+ if (chain === 'solana') {
621
+ return await this.signInWithSolana(credentials)
622
+ }
623
+
624
+ throw new Error(`@supabase/auth-js: Unsupported chain "${chain}"`)
625
+ }
626
+
627
+ private async signInWithSolana(credentials: SolanaWeb3Credentials) {
628
+ let message: string
629
+ let signature: Uint8Array
630
+
631
+ if ('message' in credentials) {
632
+ message = credentials.message
633
+ signature = credentials.signature
634
+ } else {
635
+ const { chain, wallet, statement, options } = credentials
636
+
637
+ let resolvedWallet: SolanaWallet
638
+
639
+ if (!isBrowser()) {
640
+ if (typeof wallet !== 'object' || !options?.url) {
641
+ throw new Error(
642
+ '@supabase/auth-js: Both wallet and url must be specified in non-browser environments.'
643
+ )
644
+ }
645
+
646
+ resolvedWallet = wallet
647
+ } else if (typeof wallet === 'object') {
648
+ resolvedWallet = wallet
649
+ } else {
650
+ const windowAny = window as any
651
+
652
+ if (
653
+ 'solana' in windowAny &&
654
+ typeof windowAny.solana === 'object' &&
655
+ (('signIn' in windowAny.solana && typeof windowAny.solana.signIn === 'function') ||
656
+ ('signMessage' in windowAny.solana &&
657
+ typeof windowAny.solana.signMessage === 'function'))
658
+ ) {
659
+ resolvedWallet = windowAny.solana
660
+ } else {
661
+ throw new Error(
662
+ `@supabase/auth-js: No compatible Solana wallet interface on the window object (window.solana) detected. Make sure the user already has a wallet installed and connected for this app. Prefer passing the wallet interface object directly to signInWithWeb3({ chain: 'solana', wallet: resolvedUserWallet }) instead.`
663
+ )
664
+ }
665
+ }
666
+
667
+ const url = new URL(options?.url ?? window.location.href)
668
+
669
+ if ('signIn' in resolvedWallet && resolvedWallet.signIn) {
670
+ const output = await resolvedWallet.signIn({
671
+ issuedAt: new Date().toISOString(),
672
+
673
+ ...options?.signInWithSolana,
674
+
675
+ // non-overridable properties
676
+ version: '1',
677
+ domain: url.host,
678
+ uri: url.href,
679
+
680
+ ...(statement ? { statement } : null),
681
+ })
682
+
683
+ let outputToProcess: any
684
+
685
+ if (Array.isArray(output) && output[0] && typeof output[0] === 'object') {
686
+ outputToProcess = output[0]
687
+ } else if (
688
+ output &&
689
+ typeof output === 'object' &&
690
+ 'signedMessage' in output &&
691
+ 'signature' in output
692
+ ) {
693
+ outputToProcess = output
694
+ } else {
695
+ throw new Error('@supabase/auth-js: Wallet method signIn() returned unrecognized value')
696
+ }
697
+
698
+ if (
699
+ 'signedMessage' in outputToProcess &&
700
+ 'signature' in outputToProcess &&
701
+ (typeof outputToProcess.signedMessage === 'string' ||
702
+ outputToProcess.signedMessage instanceof Uint8Array) &&
703
+ outputToProcess.signature instanceof Uint8Array
704
+ ) {
705
+ message =
706
+ typeof outputToProcess.signedMessage === 'string'
707
+ ? outputToProcess.signedMessage
708
+ : new TextDecoder().decode(outputToProcess.signedMessage)
709
+ signature = outputToProcess.signature
710
+ } else {
711
+ throw new Error(
712
+ '@supabase/auth-js: Wallet method signIn() API returned object without signedMessage and signature fields'
713
+ )
714
+ }
715
+ } else {
716
+ if (
717
+ !('signMessage' in resolvedWallet) ||
718
+ typeof resolvedWallet.signMessage !== 'function' ||
719
+ !('publicKey' in resolvedWallet) ||
720
+ typeof resolvedWallet !== 'object' ||
721
+ !resolvedWallet.publicKey ||
722
+ !('toBase58' in resolvedWallet.publicKey) ||
723
+ typeof resolvedWallet.publicKey.toBase58 !== 'function'
724
+ ) {
725
+ throw new Error(
726
+ '@supabase/auth-js: Wallet does not have a compatible signMessage() and publicKey.toBase58() API'
727
+ )
728
+ }
729
+
730
+ message = [
731
+ `${url.host} wants you to sign in with your Solana account:`,
732
+ resolvedWallet.publicKey.toBase58(),
733
+ ...(statement ? ['', statement, ''] : ['']),
734
+ 'Version: 1',
735
+ `URI: ${url.href}`,
736
+ `Issued At: ${options?.signInWithSolana?.issuedAt ?? new Date().toISOString()}`,
737
+ ...(options?.signInWithSolana?.notBefore
738
+ ? [`Not Before: ${options.signInWithSolana.notBefore}`]
739
+ : []),
740
+ ...(options?.signInWithSolana?.expirationTime
741
+ ? [`Expiration Time: ${options.signInWithSolana.expirationTime}`]
742
+ : []),
743
+ ...(options?.signInWithSolana?.chainId
744
+ ? [`Chain ID: ${options.signInWithSolana.chainId}`]
745
+ : []),
746
+ ...(options?.signInWithSolana?.nonce ? [`Nonce: ${options.signInWithSolana.nonce}`] : []),
747
+ ...(options?.signInWithSolana?.requestId
748
+ ? [`Request ID: ${options.signInWithSolana.requestId}`]
749
+ : []),
750
+ ...(options?.signInWithSolana?.resources?.length
751
+ ? [
752
+ 'Resources',
753
+ ...options.signInWithSolana.resources.map((resource) => `- ${resource}`),
754
+ ]
755
+ : []),
756
+ ].join('\n')
757
+
758
+ const maybeSignature = await resolvedWallet.signMessage(
759
+ new TextEncoder().encode(message),
760
+ 'utf8'
761
+ )
762
+
763
+ if (!maybeSignature || !(maybeSignature instanceof Uint8Array)) {
764
+ throw new Error(
765
+ '@supabase/auth-js: Wallet signMessage() API returned an recognized value'
766
+ )
767
+ }
768
+
769
+ signature = maybeSignature
770
+ }
771
+ }
772
+
773
+ try {
774
+ const { data, error } = await _request(
775
+ this.fetch,
776
+ 'POST',
777
+ `${this.url}/token?grant_type=web3`,
778
+ {
779
+ headers: this.headers,
780
+ body: {
781
+ chain: 'solana',
782
+ message,
783
+ signature: bytesToBase64URL(signature),
784
+
785
+ ...(credentials.options?.captchaToken
786
+ ? { gotrue_meta_security: { captcha_token: credentials.options?.captchaToken } }
787
+ : null),
788
+ },
789
+ xform: _sessionResponse,
790
+ }
791
+ )
792
+ if (error) {
793
+ throw error
794
+ }
795
+ if (!data || !data.session || !data.user) {
796
+ return {
797
+ data: { user: null, session: null },
798
+ error: new AuthInvalidTokenResponseError(),
799
+ }
800
+ }
801
+ if (data.session) {
802
+ await this._saveSession(data.session)
803
+ await this._notifyAllSubscribers('SIGNED_IN', data.session)
804
+ }
805
+ return { data: { ...data }, error }
806
+ } catch (error) {
807
+ if (isAuthError(error)) {
808
+ return { data: { user: null, session: null }, error }
809
+ }
810
+
811
+ throw error
812
+ }
813
+ }
814
+
604
815
  private async _exchangeCodeForSession(authCode: string): Promise<
605
816
  | {
606
817
  data: { session: Session; user: User; redirectType: string | null }
@@ -288,3 +288,19 @@ export function stringToUint8Array(str: string): Uint8Array {
288
288
  stringToUTF8(str, (byte: number) => result.push(byte))
289
289
  return new Uint8Array(result)
290
290
  }
291
+
292
+ export function bytesToBase64URL(bytes: Uint8Array) {
293
+ const result: string[] = []
294
+ const state = { queue: 0, queuedBits: 0 }
295
+
296
+ const onChar = (char: string) => {
297
+ result.push(char)
298
+ }
299
+
300
+ bytes.forEach((byte) => byteToBase64URL(byte, state, onChar))
301
+
302
+ // always call with `null` after processing all bytes
303
+ byteToBase64URL(null, state, onChar)
304
+
305
+ return result.join('')
306
+ }
package/src/lib/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AuthError } from './errors'
2
2
  import { Fetch } from './fetch'
3
+ import type { SolanaSignInInput, SolanaSignInOutput } from '@solana/wallet-standard-features'
3
4
 
4
5
  /** One of the providers supported by GoTrue. */
5
6
  export type Provider =
@@ -521,6 +522,7 @@ export type SignUpWithPasswordCredentials =
521
522
  channel?: 'sms' | 'whatsapp'
522
523
  }
523
524
  }
525
+
524
526
  export type SignInWithPasswordCredentials =
525
527
  | {
526
528
  /** The user's email address. */
@@ -612,6 +614,54 @@ export type SignInWithIdTokenCredentials = {
612
614
  }
613
615
  }
614
616
 
617
+ export type SolanaWallet = {
618
+ signIn?: (...inputs: SolanaSignInInput[]) => Promise<SolanaSignInOutput | SolanaSignInOutput[]>
619
+ publicKey?: {
620
+ toBase58: () => string
621
+ } | null
622
+
623
+ signMessage?: (message: Uint8Array, encoding?: 'utf8' | string) => Promise<Uint8Array> | undefined
624
+ }
625
+
626
+ export type SolanaWeb3Credentials =
627
+ | {
628
+ chain: 'solana'
629
+
630
+ /** Wallet interface to use. If not specified will default to `window.solana`. */
631
+ wallet?: SolanaWallet
632
+
633
+ /** Optional statement to include in the Sign in with Solana message. Must not include new line characters. Most wallets like Phantom **require specifying a statement!** */
634
+ statement?: string
635
+
636
+ options?: {
637
+ /** URL to use with the wallet interface. Some wallets do not allow signing a message for URLs different from the current page. */
638
+ url?: string
639
+
640
+ /** Verification token received when the user completes the captcha on the site. */
641
+ captchaToken?: string
642
+
643
+ signInWithSolana?: Partial<
644
+ Omit<SolanaSignInInput, 'version' | 'chain' | 'domain' | 'uri' | 'statement'>
645
+ >
646
+ }
647
+ }
648
+ | {
649
+ chain: 'solana'
650
+
651
+ /** Sign in with Solana compatible message. Must include `Issued At`, `URI` and `Version`. */
652
+ message: string
653
+
654
+ /** Ed25519 signature of the message. */
655
+ signature: Uint8Array
656
+
657
+ options?: {
658
+ /** Verification token received when the user completes the captcha on the site. */
659
+ captchaToken?: string
660
+ }
661
+ }
662
+
663
+ export type Web3Credentials = SolanaWeb3Credentials
664
+
615
665
  export type VerifyOtpParams = VerifyMobileOtpParams | VerifyEmailOtpParams | VerifyTokenHashParams
616
666
  export interface VerifyMobileOtpParams {
617
667
  /** The user's phone number. */
@@ -1 +1 @@
1
- export const version = '2.69.1'
1
+ export const version = '2.70.0-rc.2'