257 lines
6.9 KiB
Go
257 lines
6.9 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/gobwas/glob"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/supabase/auth/internal/conf"
|
|
"github.com/supabase/auth/internal/crypto"
|
|
"github.com/supabase/auth/internal/models"
|
|
)
|
|
|
|
type MailTestSuite struct {
|
|
suite.Suite
|
|
API *API
|
|
Config *conf.GlobalConfiguration
|
|
}
|
|
|
|
func TestMail(t *testing.T) {
|
|
api, config, err := setupAPIForTest()
|
|
require.NoError(t, err)
|
|
|
|
ts := &MailTestSuite{
|
|
API: api,
|
|
Config: config,
|
|
}
|
|
defer api.db.Close()
|
|
|
|
suite.Run(t, ts)
|
|
}
|
|
|
|
func (ts *MailTestSuite) SetupTest() {
|
|
models.TruncateAll(ts.API.db)
|
|
|
|
ts.Config.Mailer.SecureEmailChangeEnabled = true
|
|
|
|
// Create User
|
|
u, err := models.NewUser("12345678", "test@example.com", "password", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error creating new user model")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error saving new user")
|
|
}
|
|
|
|
func (ts *MailTestSuite) TestValidateEmail() {
|
|
cases := []struct {
|
|
desc string
|
|
email string
|
|
expectedEmail string
|
|
expectedError error
|
|
}{
|
|
{
|
|
desc: "valid email",
|
|
email: "test@example.com",
|
|
expectedEmail: "test@example.com",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "email should be normalized",
|
|
email: "TEST@EXAMPLE.COM",
|
|
expectedEmail: "test@example.com",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
desc: "empty email should return error",
|
|
email: "",
|
|
expectedEmail: "",
|
|
expectedError: badRequestError(ErrorCodeValidationFailed, "An email address is required"),
|
|
},
|
|
{
|
|
desc: "email length exceeds 255 characters",
|
|
// email has 256 characters
|
|
email: "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest@example.com",
|
|
expectedEmail: "",
|
|
expectedError: badRequestError(ErrorCodeValidationFailed, "An email address is too long"),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
ts.Run(c.desc, func() {
|
|
email, err := ts.API.validateEmail(c.email)
|
|
require.Equal(ts.T(), c.expectedError, err)
|
|
require.Equal(ts.T(), c.expectedEmail, email)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (ts *MailTestSuite) TestGenerateLink() {
|
|
// create admin jwt
|
|
claims := &AccessTokenClaims{
|
|
Role: "supabase_admin",
|
|
}
|
|
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(ts.Config.JWT.Secret))
|
|
require.NoError(ts.T(), err, "Error generating admin jwt")
|
|
|
|
ts.setURIAllowListMap("http://localhost:8000/**")
|
|
// create test cases
|
|
cases := []struct {
|
|
Desc string
|
|
Body GenerateLinkParams
|
|
ExpectedCode int
|
|
ExpectedResponse map[string]interface{}
|
|
}{
|
|
{
|
|
Desc: "Generate signup link for new user",
|
|
Body: GenerateLinkParams{
|
|
Email: "new_user@example.com",
|
|
Password: "secret123",
|
|
Type: "signup",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate signup link for existing user",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
Password: "secret123",
|
|
Type: "signup",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate signup link with custom redirect url",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
Password: "secret123",
|
|
Type: "signup",
|
|
RedirectTo: "http://localhost:8000/welcome",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": "http://localhost:8000/welcome",
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate magic link",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
Type: "magiclink",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate invite link",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
Type: "invite",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate recovery link",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
Type: "recovery",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate email change link",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
NewEmail: "new@example.com",
|
|
Type: "email_change_current",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
{
|
|
Desc: "Generate email change link",
|
|
Body: GenerateLinkParams{
|
|
Email: "test@example.com",
|
|
NewEmail: "new@example.com",
|
|
Type: "email_change_new",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
ExpectedResponse: map[string]interface{}{
|
|
"redirect_to": ts.Config.SiteURL,
|
|
},
|
|
},
|
|
}
|
|
|
|
customDomainUrl, err := url.ParseRequestURI("https://example.gotrue.com")
|
|
require.NoError(ts.T(), err)
|
|
|
|
originalHosts := ts.API.config.Mailer.ExternalHosts
|
|
ts.API.config.Mailer.ExternalHosts = []string{
|
|
"example.gotrue.com",
|
|
}
|
|
|
|
for _, c := range cases {
|
|
ts.Run(c.Desc, func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.Body))
|
|
req := httptest.NewRequest(http.MethodPost, customDomainUrl.String()+"/admin/generate_link", &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
w := httptest.NewRecorder()
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
|
|
require.Equal(ts.T(), c.ExpectedCode, w.Code)
|
|
|
|
data := make(map[string]interface{})
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
require.Contains(ts.T(), data, "action_link")
|
|
require.Contains(ts.T(), data, "email_otp")
|
|
require.Contains(ts.T(), data, "hashed_token")
|
|
require.Contains(ts.T(), data, "redirect_to")
|
|
require.Equal(ts.T(), c.Body.Type, data["verification_type"])
|
|
|
|
// check if redirect_to is correct
|
|
require.Equal(ts.T(), c.ExpectedResponse["redirect_to"], data["redirect_to"])
|
|
|
|
// check if hashed_token matches hash function of email and the raw otp
|
|
require.Equal(ts.T(), crypto.GenerateTokenHash(c.Body.Email, data["email_otp"].(string)), data["hashed_token"])
|
|
|
|
// check if the host used in the email link matches the initial request host
|
|
u, err := url.ParseRequestURI(data["action_link"].(string))
|
|
require.NoError(ts.T(), err)
|
|
require.Equal(ts.T(), req.Host, u.Host)
|
|
})
|
|
}
|
|
|
|
ts.API.config.Mailer.ExternalHosts = originalHosts
|
|
}
|
|
|
|
func (ts *MailTestSuite) setURIAllowListMap(uris ...string) {
|
|
for _, uri := range uris {
|
|
g := glob.MustCompile(uri, '.', '/')
|
|
ts.Config.URIAllowListMap[uri] = g
|
|
}
|
|
}
|