@@ -3,9 +3,13 @@ package supabase
33import (
44 "bytes"
55 "context"
6+ "crypto/rand"
7+ "crypto/sha256"
8+ "encoding/base64"
69 "encoding/json"
710 "errors"
811 "fmt"
12+ "io"
913 "net/http"
1014 "time"
1115
@@ -59,18 +63,24 @@ func (a *Auth) SignUp(ctx context.Context, credentials UserCredentials) (*User,
5963}
6064
6165type AuthenticatedDetails struct {
62- AccessToken string `json:"access_token"`
63- TokenType string `json:"token_type"`
64- ExpiresIn int `json:"expires_in"`
65- RefreshToken string `json:"refresh_token"`
66- User User `json:"user"`
66+ AccessToken string `json:"access_token"`
67+ TokenType string `json:"token_type"`
68+ ExpiresIn int `json:"expires_in"`
69+ RefreshToken string `json:"refresh_token"`
70+ User User `json:"user"`
71+ ProviderToken string `json:"provider_token"`
72+ ProviderRefreshToken string `json:"provider_refresh_token"`
6773}
6874
6975type authenticationError struct {
7076 Error string `json:"error"`
7177 ErrorDescription string `json:"error_description"`
7278}
7379
80+ type exchangeError struct {
81+ Message string `json:"msg"`
82+ }
83+
7484// SignIn enters the user credentials and returns the current user if succeeded.
7585func (a * Auth ) SignIn (ctx context.Context , credentials UserCredentials ) (* AuthenticatedDetails , error ) {
7686 reqBody , _ := json .Marshal (credentials )
@@ -116,6 +126,33 @@ func (a *Auth) RefreshUser(ctx context.Context, userToken string, refreshToken s
116126 return & res , nil
117127}
118128
129+ type ExchangeCodeOpts struct {
130+ AuthCode string `json:"auth_code"`
131+ CodeVerifier string `json:"code_verifier"`
132+ }
133+
134+ // ExchangeCode takes an auth code and PCKE verifier and returns the current user if succeeded.
135+ func (a * Auth ) ExchangeCode (ctx context.Context , opts ExchangeCodeOpts ) (* AuthenticatedDetails , error ) {
136+ reqBody , _ := json .Marshal (opts )
137+ reqURL := fmt .Sprintf ("%s/%s/token?grant_type=pkce" , a .client .BaseURL , AuthEndpoint )
138+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , reqURL , bytes .NewBuffer (reqBody ))
139+ if err != nil {
140+ return nil , err
141+ }
142+
143+ req .Header .Set ("Content-Type" , "application/json" )
144+ res := AuthenticatedDetails {}
145+ errRes := exchangeError {}
146+ hasCustomError , err := a .client .sendCustomRequest (req , & res , & errRes )
147+ if err != nil {
148+ return nil , err
149+ } else if hasCustomError {
150+ return nil , errors .New (errRes .Message )
151+ }
152+
153+ return & res , err
154+ }
155+
119156// SendMagicLink sends a link to a specific e-mail address for passwordless auth.
120157func (a * Auth ) SendMagicLink (ctx context.Context , email string ) error {
121158 reqBody , _ := json .Marshal (map [string ]string {"email" : email })
@@ -140,11 +177,20 @@ type ProviderSignInOptions struct {
140177 Provider string `url:"provider"`
141178 RedirectTo string `url:"redirect_to"`
142179 Scopes []string `url:"scopes"`
180+ FlowType FlowType
143181}
144182
183+ type FlowType string
184+
185+ const (
186+ Implicit FlowType = "implicit"
187+ PKCE FlowType = "pkce"
188+ )
189+
145190type ProviderSignInDetails struct {
146- URL string `json:"url"`
147- Provider string `json:"provider"`
191+ URL string `json:"url"`
192+ Provider string `json:"provider"`
193+ CodeVerifier string `json:"code_verifier"`
148194}
149195
150196// SignInWithProvider returns a URL for signing in via OAuth
@@ -154,10 +200,30 @@ func (a *Auth) SignInWithProvider(opts ProviderSignInOptions) (*ProviderSignInDe
154200 return nil , err
155201 }
156202
203+ if opts .FlowType == PKCE {
204+ p , err := generatePKCEParams ()
205+ if err != nil {
206+ return nil , err
207+ }
208+
209+ params .Add ("code_challenge" , p .Challenge )
210+ params .Add ("code_challenge_method" , p .ChallengeMethod )
211+
212+ details := ProviderSignInDetails {
213+ URL : fmt .Sprintf ("%s/%s/authorize?%s" , a .client .BaseURL , AuthEndpoint , params .Encode ()),
214+ Provider : opts .Provider ,
215+ CodeVerifier : p .Verifier ,
216+ }
217+
218+ return & details , nil
219+ }
220+
221+ // Implicit flow
157222 details := ProviderSignInDetails {
158223 URL : fmt .Sprintf ("%s/%s/authorize?%s" , a .client .BaseURL , AuthEndpoint , params .Encode ()),
159224 Provider : opts .Provider ,
160225 }
226+
161227 return & details , nil
162228}
163229
@@ -257,3 +323,27 @@ func (a *Auth) InviteUserByEmail(ctx context.Context, email string) (*User, erro
257323
258324 return & res , nil
259325}
326+
327+ // adapted from https://go-review.googlesource.com/c/oauth2/+/463979/9/pkce.go#64
328+ type PKCEParams struct {
329+ Challenge string
330+ ChallengeMethod string
331+ Verifier string
332+ }
333+
334+ func generatePKCEParams () (* PKCEParams , error ) {
335+ data := make ([]byte , 32 )
336+ if _ , err := io .ReadFull (rand .Reader , data ); err != nil {
337+ return nil , err
338+ }
339+
340+ // RawURLEncoding since "code challenge can only contain alphanumeric characters, hyphens, periods, underscores and tildes"
341+ verifier := base64 .RawURLEncoding .EncodeToString (data )
342+ sha := sha256 .Sum256 ([]byte (verifier ))
343+ challenge := base64 .RawURLEncoding .EncodeToString (sha [:])
344+ return & PKCEParams {
345+ Challenge : challenge ,
346+ ChallengeMethod : "S256" ,
347+ Verifier : verifier ,
348+ }, nil
349+ }
0 commit comments