Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permissions:
jobs:
fast-forward:
if: |
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.head.ref == 'develop' &&
github.event.pull_request.base.ref == 'main' &&
github.event.review.state == 'approved'
Expand Down
14 changes: 9 additions & 5 deletions internal/logout/logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"testing"

"github.com/spf13/afero"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/supabase/cli/internal/testing/apitest"
"github.com/supabase/cli/internal/testing/fstest"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/credentials"
"github.com/zalando/go-keyring"
Expand All @@ -20,7 +20,8 @@ func TestLogoutCommand(t *testing.T) {

t.Run("login with token and logout", func(t *testing.T) {
keyring.MockInitWithError(keyring.ErrUnsupportedPlatform)
t.Cleanup(fstest.MockStdin(t, "y"))
viper.Set("YES", true)
t.Cleanup(viper.Reset)
// Setup in-memory fs
fsys := afero.NewMemMapFs()
require.NoError(t, utils.SaveAccessToken(token, fsys))
Expand All @@ -35,10 +36,11 @@ func TestLogoutCommand(t *testing.T) {

t.Run("removes all Supabase CLI credentials", func(t *testing.T) {
keyring.MockInit()
viper.Set("YES", true)
t.Cleanup(viper.Reset)
require.NoError(t, credentials.StoreProvider.Set(utils.CurrentProfile.Name, token))
require.NoError(t, credentials.StoreProvider.Set("project1", "password1"))
require.NoError(t, credentials.StoreProvider.Set("project2", "password2"))
t.Cleanup(fstest.MockStdin(t, "y"))
// Run test
err := Run(context.Background(), os.Stdout, afero.NewMemMapFs())
// Check error
Expand Down Expand Up @@ -70,7 +72,8 @@ func TestLogoutCommand(t *testing.T) {

t.Run("exits 0 if not logged in", func(t *testing.T) {
keyring.MockInit()
t.Cleanup(fstest.MockStdin(t, "y"))
viper.Set("YES", true)
t.Cleanup(viper.Reset)
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Run test
Expand All @@ -81,7 +84,8 @@ func TestLogoutCommand(t *testing.T) {

t.Run("throws error on failure to delete", func(t *testing.T) {
keyring.MockInitWithError(keyring.ErrNotFound)
t.Cleanup(fstest.MockStdin(t, "y"))
viper.Set("YES", true)
t.Cleanup(viper.Reset)
// Setup empty home directory
t.Setenv("HOME", "")
// Setup in-memory fs
Expand Down
131 changes: 131 additions & 0 deletions pkg/api/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/api/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 67 additions & 6 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
_ "embed"
"encoding"
"encoding/base64"
"encoding/json"
"fmt"
Expand All @@ -16,13 +17,15 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"text/template"
"time"
"unicode"

"github.com/BurntSushi/toml"
"github.com/docker/go-units"
Expand Down Expand Up @@ -458,17 +461,15 @@ func (c *config) Eject(w io.Writer) error {

// Loads custom config file to struct fields tagged with toml.
func (c *config) loadFromFile(filename string, fsys fs.FS) error {
v := viper.NewWithOptions(
viper.ExperimentalBindStruct(),
viper.EnvKeyReplacer(strings.NewReplacer(".", "_")),
)
v.SetEnvPrefix("SUPABASE")
v.AutomaticEnv()
v := viper.New()
if err := c.mergeDefaultValues(v); err != nil {
return err
} else if err := mergeFileConfig(v, filename, fsys); err != nil {
return err
}
if err := bindUserConfigEnv(v, reflect.TypeOf(*c), ""); err != nil {
return err
}
// Find [remotes.*] block to override base config
idToName := map[string]string{}
for name, remote := range v.GetStringMap("remotes") {
Expand All @@ -488,6 +489,66 @@ func (c *config) loadFromFile(filename string, fsys fs.FS) error {
return c.load(v)
}

func bindUserConfigEnv(v *viper.Viper, t reflect.Type, prefix string) error {
textUnmarshaler := reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Anonymous {
if err := bindUserConfigEnv(v, field.Type, prefix); err != nil {
return err
}
continue
}
if field.PkgPath != "" {
continue
}
if tag := strings.Split(field.Tag.Get("toml"), ",")[0]; tag == "-" {
continue
}
key := strings.Split(field.Tag.Get("json"), ",")[0]
if key == "-" {
continue
} else if key == "" {
key = toSnakeCase(field.Name)
}
if len(prefix) > 0 {
key = prefix + "." + key
}
fieldType := field.Type
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
if fieldType.Kind() == reflect.Map {
continue
}
if fieldType.Kind() == reflect.Struct && !fieldType.Implements(textUnmarshaler) && !reflect.PointerTo(fieldType).Implements(textUnmarshaler) {
if err := bindUserConfigEnv(v, fieldType, key); err != nil {
return err
}
continue
}
envKey := "SUPABASE_" + strings.ToUpper(strings.ReplaceAll(key, ".", "_"))
if value, ok := os.LookupEnv(envKey); ok {
v.Set(key, value)
}
}
return nil
}

func toSnakeCase(s string) string {
var b strings.Builder
for i, r := range s {
if unicode.IsUpper(r) {
if i > 0 {
b.WriteByte('_')
}
r = unicode.ToLower(r)
}
b.WriteRune(r)
}
return b.String()
}

func (c *config) mergeDefaultValues(v *viper.Viper) error {
v.SetConfigType("toml")
var buf bytes.Buffer
Expand Down
15 changes: 15 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ func TestRemoteOverride(t *testing.T) {
})
}

func TestEnvOverridesSkipInternalFields(t *testing.T) {
config := NewConfig()
fsys := fs.MapFS{
"supabase/config.toml": &fs.MapFile{Data: testInitConfigEmbed},
"supabase/templates/invite.html": &fs.MapFile{},
}
t.Setenv("SUPABASE_HOSTNAME", "evil.example.com")
t.Setenv("SUPABASE_AUTH_SITE_URL", "http://preview.com")
t.Setenv("AUTH_SEND_SMS_SECRETS", "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==")

require.NoError(t, config.Load("", fsys))
assert.Equal(t, "127.0.0.1", config.Hostname)
assert.Equal(t, "http://preview.com", config.Auth.SiteUrl)
}

func TestFileSizeLimitConfigParsing(t *testing.T) {
t.Run("test file size limit parsing number", func(t *testing.T) {
var testConfig config
Expand Down
Loading