Repository: supabase/cli
Pull request #947 (2023-03-21 09:17:12)

feat: support custom role and jwt keys
## What kind of change does this PR introduce?

feature

## What is the new behavior?

Additional jwt tokens can be injected into kong.yaml via env var. For example, the following env var allows jwt token for role `read_only` to be authenticated with kong.

```bash
SUPABASE_AUTH_READ_ONLY_KEY=<jwt_token> supabase start
```

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/internal/start/start.go b/internal/start/start.go
index 93c14326..2215c529 100644
--- a/internal/start/start.go
+++ b/internal/start/start.go
@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"regexp"
 	"strconv"
 	"strings"
 	"text/template"
@@ -57,13 +58,37 @@ func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignore
 	return nil
 }
 
+type kongConfig struct {
+	ProjectId string
+	ApiKeys   map[string]string
+}
+
 var (
-	// TODO: Unhardcode keys
 	//go:embed templates/kong_config
 	kongConfigEmbed    string
 	kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
+	customRoleKey      = regexp.MustCompile(`^SUPABASE_AUTH_(.*)_KEY$`)
 )
 
+func NewKongConfig() kongConfig {
+	config := kongConfig{
+		ProjectId: utils.Config.ProjectId,
+		ApiKeys: map[string]string{
+			"anon":         utils.Config.Auth.AnonKey,
+			"service_role": utils.Config.Auth.ServiceRoleKey,
+		},
+	}
+	for _, kv := range os.Environ() {
+		apikey := strings.Split(kv, "=")
+		match := customRoleKey.FindStringSubmatch(apikey[0])
+		if len(match) == 2 {
+			role := strings.ToLower(match[1])
+			config.ApiKeys[role] = apikey[1]
+		}
+	}
+	return config
+}
+
 func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers []string, options ...func(*pgx.ConnConfig)) error {
 	excluded := make(map[string]bool)
 	for _, name := range excludedContainers {
@@ -101,11 +126,7 @@ func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers
 	// Start Kong.
 	if !isContainerExcluded(utils.KongImage, excluded) {
 		var kongConfigBuf bytes.Buffer
-		if err := kongConfigTemplate.Execute(&kongConfigBuf, struct{ ProjectId, AnonKey, ServiceRoleKey string }{
-			ProjectId:      utils.Config.ProjectId,
-			AnonKey:        utils.Config.Auth.AnonKey,
-			ServiceRoleKey: utils.Config.Auth.ServiceRoleKey,
-		}); err != nil {
+		if err := kongConfigTemplate.Execute(&kongConfigBuf, NewKongConfig()); err != nil {
 			return err
 		}
 
diff --git a/internal/start/templates/kong_config b/internal/start/templates/kong_config
index a09b4faa..c80ce7fd 100644
--- a/internal/start/templates/kong_config
+++ b/internal/start/templates/kong_config
@@ -114,9 +114,8 @@ services:
         paths:
           - /functions/v1/
 consumers:
-  - username: anon
+{{range $key, $val := .ApiKeys}}
+  - username: {{$key}}
     keyauth_credentials:
-      - key: {{ .AnonKey }}
-  - username: service_role
-    keyauth_credentials:
-      - key: {{ .ServiceRoleKey }}
+      - key: {{$val}}
+{{end}}


Test patch:
diff --git a/internal/start/start_test.go b/internal/start/start_test.go
index 08f5b8c5..5412d406 100644
--- a/internal/start/start_test.go
+++ b/internal/start/start_test.go
@@ -257,3 +257,14 @@ func TestDatabaseStart(t *testing.T) {
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 }
+
+func TestKongConfig(t *testing.T) {
+	utils.Config.Auth.AnonKey = "abc"
+	utils.Config.Auth.ServiceRoleKey = "123"
+	os.Setenv("SUPABASE_AUTH_CUSTOM_ROLE_KEY", "test")
+	config := NewKongConfig()
+	assert.Equal(t, config.ApiKeys["anon"], "abc")
+	assert.Equal(t, config.ApiKeys["service_role"], "123")
+	assert.Equal(t, config.ApiKeys["custom_role"], "test")
+	assert.NoError(t, kongConfigTemplate.Execute(os.Stderr, config))
+}