From 5662c9d4473ae9d06fbb4910ae6e1d25886e271a Mon Sep 17 00:00:00 2001 From: Kieron Woodhouse Date: Tue, 8 Nov 2022 11:43:21 +0000 Subject: [PATCH] Add POST /sso and allow Captcha token to be specified --- README.md | 3 -- api.go | 48 ++++++++++++++++++++++ endpoints/sso.go | 40 ++++++++++++++++++ integration_test/setup/docker-compose.yaml | 2 +- types/api.go | 28 +++++++++++++ types/security.go | 9 ++++ 6 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 endpoints/sso.go create mode 100644 types/security.go diff --git a/README.md b/README.md index 8270e88..24d4b3e 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,6 @@ This library is a pre-release work in progress. It has not been thoroughly teste The endpoints for SSO SAML are not tested and `POST /sso/saml/acs` does not provide request and response types. If you need additional support for SSO SAML, please create an issue or a pull request. -Still required for V1 release: -- [ ] Support for Captcha tokens - ## Quick start ### Install diff --git a/api.go b/api.go index 49212a4..f1e6715 100644 --- a/api.go +++ b/api.go @@ -278,4 +278,52 @@ type Client interface { // which is used to verify the token associated to the user. It also returns a // JSON response rather than a redirect. VerifyForUser(req types.VerifyForUserRequest) (*types.VerifyForUserResponse, error) + + // GET /sso/saml/metadata + // + // Get the SAML metadata for the configured SAML provider. + // + // If successful, the server returns an XML response. Making sense of this is + // outside the scope of this client, so it is simply returned as []byte. + SAMLMetadata() ([]byte, error) + // POST /sso/saml/acs + // + // Implements the main Assertion Consumer Service endpoint behavior. + // + // This client does not provide a typed endpoint for SAML ACS. This method is + // provided for convenience and will simply POST your HTTP request to the + // endpoint and return the response. + // + // For required parameters, see the SAML spec or the GoTrue implementation + // of this endpoint. + // + // The server may issue redirects. Using the default HTTP client, this method + // will follow those redirects and return the final HTTP response. Should you + // prefer the client not to follow redirects, you can provide a custom HTTP + // client using WithClient(). See the example below. + // + // Example: + // c := http.Client{ + // CheckRedirect: func(req *http.Request, via []*http.Request) error { + // return http.ErrUseLastResponse + // }, + // } + SAMLACS(req *http.Request) (*http.Response, error) + + // POST /sso + // + // Initiate an SSO session with the given provider. + // + // If successful, the server returns a redirect to the provider's authorization + // URL. The client will follow it and return the final response. Should you + // prefer the client not to follow redirects, you can provide a custom HTTP + // client using WithClient(). See the example below. + // + // Example: + // c := http.Client{ + // CheckRedirect: func(req *http.Request, via []*http.Request) error { + // return http.ErrUseLastResponse + // }, + // } + SSO(req types.SSORequest) (*http.Response, error) } diff --git a/endpoints/sso.go b/endpoints/sso.go new file mode 100644 index 0000000..6a42ec6 --- /dev/null +++ b/endpoints/sso.go @@ -0,0 +1,40 @@ +package endpoints + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/kwoodhouse93/gotrue-go/types" +) + +const ssoPath = "/sso" + +// POST /sso +// +// Initiate an SSO session with the given provider. +// +// If successful, the server returns a redirect to the provider's authorization +// URL. The client will follow it and return the final response. Should you +// prefer the client not to follow redirects, you can provide a custom HTTP +// client using WithClient(). See the example below. +// +// Example: +// c := http.Client{ +// CheckRedirect: func(req *http.Request, via []*http.Request) error { +// return http.ErrUseLastResponse +// }, +// } +func (c *Client) SSO(req types.SSORequest) (*http.Response, error) { + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + + r, err := c.newRequest(http.MethodPost, ssoPath, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + + return c.client.Do(r) +} diff --git a/integration_test/setup/docker-compose.yaml b/integration_test/setup/docker-compose.yaml index 9c07863..8acaaf3 100644 --- a/integration_test/setup/docker-compose.yaml +++ b/integration_test/setup/docker-compose.yaml @@ -5,7 +5,7 @@ services: container_name: gotrue depends_on: - postgres - image: supabase/gotrue:v2.25.1 + image: supabase/gotrue:v2.27.0 restart: on-failure ports: - '9999:9999' diff --git a/types/api.go b/types/api.go index 0f4f7ad..5b00471 100644 --- a/types/api.go +++ b/types/api.go @@ -353,6 +353,9 @@ type InviteResponse struct { // DEPRECATED: Use /otp with Email and CreateUser=true instead of /magiclink. type MagiclinkRequest struct { Email string `json:"email"` + + // Provide Captcha token if enabled. + SecurityEmbed } type OTPRequest struct { @@ -360,10 +363,16 @@ type OTPRequest struct { Phone string `json:"phone"` CreateUser bool `json:"create_user"` Data map[string]interface{} `json:"data"` + + // Provide Captcha token if enabled. + SecurityEmbed } type RecoverRequest struct { Email string `json:"email"` + + // Provide Captcha token if enabled. + SecurityEmbed } type ExternalProviders struct { @@ -404,6 +413,9 @@ type SignupRequest struct { Phone string `json:"phone,omitempty"` Password string `json:"password,omitempty"` Data map[string]interface{} `json:"data,omitempty"` + + // Provide Captcha token if enabled. + SecurityEmbed } type SignupResponse struct { @@ -414,6 +426,16 @@ type SignupResponse struct { Session } +type SSORequest struct { + // Use either ProviderID or Domain. + ProviderID uuid.UUID `json:"provider_id"` + Domain string `json:"domain"` + RedirectTo string `json:"redirect_to"` + + // Provide Captcha token if enabled. + SecurityEmbed +} + type TokenRequest struct { GrantType string `json:"-"` @@ -426,6 +448,9 @@ type TokenRequest struct { // RefreshToken is required if GrantType is 'refresh_token'. // It must not be provided if GrantType is 'password'. RefreshToken string `json:"refresh_token,omitempty"` + + // Provide Captcha token if enabled. Not required if GrantType is 'refresh_token'. + SecurityEmbed } type TokenResponse struct { @@ -489,6 +514,9 @@ type VerifyForUserRequest struct { RedirectTo string `json:"redirect_to"` Email string `json:"email"` Phone string `json:"phone"` + + // Provide Captcha token if enabled. + SecurityEmbed } type VerifyForUserResponse struct { diff --git a/types/security.go b/types/security.go new file mode 100644 index 0000000..fcdc476 --- /dev/null +++ b/types/security.go @@ -0,0 +1,9 @@ +package types + +type SecurityEmbed struct { + Security GoTrueMetaSecurity `json:"gotrue_meta_security"` +} + +type GoTrueMetaSecurity struct { + CaptchaToken string `json:"captcha_token"` +}