Skip to content

Commit

Permalink
feat(config): support global auth config update
Browse files Browse the repository at this point in the history
  • Loading branch information
sweatybridge committed Nov 5, 2024
1 parent 526da59 commit 7752c11
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 1 deletion.
20 changes: 19 additions & 1 deletion pkg/cast/cast.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cast

import "math"
import (
"math"
"strings"
)

// UintToInt converts a uint to an int, handling potential overflow
func UintToInt(value uint) int {
Expand Down Expand Up @@ -37,3 +40,18 @@ func IntToUintPtr(value *int) *uint {
func Ptr[T any](v T) *T {
return &v
}

func Val[T any](v *T, def T) T {
if v == nil {
return def
}
return *v
}

func StrToArr(v string) []string {
// Avoid returning [""] if v is empty
if len(v) == 0 {
return nil
}
return strings.Split(v, ",")
}
45 changes: 45 additions & 0 deletions pkg/config/auth.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package config

import (
"strings"
"time"

v1API "github.com/supabase/cli/pkg/api"
"github.com/supabase/cli/pkg/cast"
"github.com/supabase/cli/pkg/diff"
)

type (
Expand Down Expand Up @@ -172,3 +177,43 @@ type (
SkipNonceCheck bool `toml:"skip_nonce_check"`
}
)

func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody {
body := v1API.UpdateAuthConfigBody{
SiteUrl: cast.Ptr(a.SiteUrl),
UriAllowList: cast.Ptr(strings.Join(a.AdditionalRedirectUrls, ",")),
JwtExp: cast.Ptr(cast.UintToInt(a.JwtExpiry)),
RefreshTokenRotationEnabled: cast.Ptr(a.EnableRefreshTokenRotation),
SecurityRefreshTokenReuseInterval: cast.Ptr(cast.UintToInt(a.RefreshTokenReuseInterval)),
SecurityManualLinkingEnabled: cast.Ptr(a.EnableManualLinking),
DisableSignup: cast.Ptr(!a.EnableSignup),
ExternalAnonymousUsersEnabled: cast.Ptr(a.EnableAnonymousSignIns),
}
return body
}

func (a *auth) fromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) auth {
result := *a
result.SiteUrl = cast.Val(remoteConfig.SiteUrl, "")
result.AdditionalRedirectUrls = cast.StrToArr(cast.Val(remoteConfig.UriAllowList, ""))
result.JwtExpiry = cast.IntToUint(cast.Val(remoteConfig.JwtExp, 0))
result.EnableRefreshTokenRotation = cast.Val(remoteConfig.RefreshTokenRotationEnabled, false)
result.RefreshTokenReuseInterval = cast.IntToUint(cast.Val(remoteConfig.SecurityRefreshTokenReuseInterval, 0))
result.EnableManualLinking = cast.Val(remoteConfig.SecurityManualLinkingEnabled, false)
result.EnableSignup = !cast.Val(remoteConfig.DisableSignup, false)
result.EnableAnonymousSignIns = cast.Val(remoteConfig.ExternalAnonymousUsersEnabled, false)
return result
}

func (a *auth) DiffWithRemote(remoteConfig v1API.AuthConfigResponse) ([]byte, error) {
// Convert the config values into easily comparable remoteConfig values
currentValue, err := ToTomlBytes(a)
if err != nil {
return nil, err
}
remoteCompare, err := ToTomlBytes(a.fromRemoteAuthConfig(remoteConfig))
if err != nil {
return nil, err
}
return diff.Diff("remote[auth]", remoteCompare, "local[auth]", currentValue), nil
}
30 changes: 30 additions & 0 deletions pkg/config/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func (u *ConfigUpdater) UpdateRemoteConfig(ctx context.Context, remote baseConfi
if err := u.UpdateDbConfig(ctx, remote.ProjectId, remote.Db); err != nil {
return err
}
if err := u.UpdateAuthConfig(ctx, remote.ProjectId, remote.Auth); err != nil {
return err
}
if err := u.UpdateStorageConfig(ctx, remote.ProjectId, remote.Storage); err != nil {
return err
}
Expand Down Expand Up @@ -95,6 +98,33 @@ func (u *ConfigUpdater) UpdateDbConfig(ctx context.Context, projectRef string, c
return nil
}

func (u *ConfigUpdater) UpdateAuthConfig(ctx context.Context, projectRef string, c auth) error {
if !c.Enabled {
return nil
}
authConfig, err := u.client.V1GetAuthServiceConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to read Auth config: %w", err)
} else if authConfig.JSON200 == nil {
return errors.Errorf("unexpected status %d: %s", authConfig.StatusCode(), string(authConfig.Body))
}
authDiff, err := c.DiffWithRemote(*authConfig.JSON200)
if err != nil {
return err
} else if len(authDiff) == 0 {
fmt.Fprintln(os.Stderr, "Remote Auth config is up to date.")
return nil
}
fmt.Fprintln(os.Stderr, "Updating Auth service with config:", string(authDiff))

if resp, err := u.client.V1UpdateAuthServiceConfigWithResponse(ctx, projectRef, c.ToUpdateAuthConfigBody()); err != nil {
return errors.Errorf("failed to update Auth config: %w", err)
} else if status := resp.StatusCode(); status < 200 || status >= 300 {
return errors.Errorf("unexpected status %d: %s", status, string(resp.Body))
}
return nil
}

func (u *ConfigUpdater) UpdateStorageConfig(ctx context.Context, projectRef string, c storage) error {
if !c.Enabled {
return nil
Expand Down
72 changes: 72 additions & 0 deletions pkg/config/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,58 @@ func TestUpdateExperimentalConfig(t *testing.T) {
})
}

func TestUpdateAuthConfig(t *testing.T) {
server := "http://localhost"
client, err := v1API.NewClientWithResponses(server)
require.NoError(t, err)

t.Run("updates remote Auth config", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Setup mock server
defer gock.Off()
gock.New(server).
Get("/v1/projects/test-project/config/auth").
Reply(http.StatusOK).
JSON(v1API.AuthConfigResponse{
SiteUrl: cast.Ptr("http://localhost:3000"),
})
gock.New(server).
Patch("/v1/projects/test-project/config/auth").
Reply(http.StatusOK)
// Run test
err := updater.UpdateAuthConfig(context.Background(), "test-project", auth{Enabled: true})
// Check result
assert.NoError(t, err)
assert.True(t, gock.IsDone())
})

t.Run("skips update if no diff in Auth config", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Setup mock server
defer gock.Off()
gock.New(server).
Get("/v1/projects/test-project/config/auth").
Reply(http.StatusOK).
JSON(v1API.AuthConfigResponse{})
// Run test
err := updater.UpdateAuthConfig(context.Background(), "test-project", auth{
Enabled: true,
EnableSignup: true,
})
// Check result
assert.NoError(t, err)
assert.True(t, gock.IsDone())
})

t.Run("skips update if disabled locally", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Run test
err := updater.UpdateAuthConfig(context.Background(), "test-project", auth{})
// Check result
assert.NoError(t, err)
})
}

func TestUpdateStorageConfig(t *testing.T) {
server := "http://localhost"
client, err := v1API.NewClientWithResponses(server)
Expand Down Expand Up @@ -199,6 +251,14 @@ func TestUpdateStorageConfig(t *testing.T) {
assert.NoError(t, err)
assert.True(t, gock.IsDone())
})

t.Run("skips update if disabled locally", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Run test
err := updater.UpdateStorageConfig(context.Background(), "test-project", storage{})
// Check result
assert.NoError(t, err)
})
}

func TestUpdateRemoteConfig(t *testing.T) {
Expand Down Expand Up @@ -233,6 +293,14 @@ func TestUpdateRemoteConfig(t *testing.T) {
JSON(v1API.PostgresConfigResponse{
MaxConnections: cast.Ptr(cast.UintToInt(100)),
})
// Auth config
gock.New(server).
Get("/v1/projects/test-project/config/auth").
Reply(http.StatusOK).
JSON(v1API.AuthConfigResponse{})
gock.New(server).
Patch("/v1/projects/test-project/config/auth").
Reply(http.StatusOK)
// Storage config
gock.New(server).
Get("/v1/projects/test-project/config/storage").
Expand All @@ -259,6 +327,10 @@ func TestUpdateRemoteConfig(t *testing.T) {
MaxConnections: cast.Ptr(cast.IntToUint(100)),
},
},
Auth: auth{
Enabled: true,
SiteUrl: "http://localhost:3000",
},
Storage: storage{
Enabled: true,
FileSizeLimit: 100,
Expand Down

0 comments on commit 7752c11

Please sign in to comment.