Skip to content

Commit

Permalink
chore: follow documentation for defining config
Browse files Browse the repository at this point in the history
  • Loading branch information
sweatybridge committed Jul 5, 2023
1 parent 0d21aa2 commit 477e76f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 119 deletions.
202 changes: 86 additions & 116 deletions internal/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,6 @@ var (
//go:embed templates/initial_schemas/15.sql
InitialSchemaPg15Sql string

authExternalProviders = []string{
"apple",
"azure",
"bitbucket",
"discord",
"facebook",
"github",
"gitlab",
"google",
"keycloak",
"linkedin",
"notion",
"twitch",
"twitter",
"slack",
"spotify",
"workos",
"zoom",
}

//go:embed templates/init_config.toml
initConfigEmbed string
initConfigTemplate = template.Must(template.New("initConfig").Parse(initConfigEmbed))
Expand All @@ -82,17 +62,60 @@ func (s *sizeInBytes) UnmarshalText(text []byte) error {
return err
}

var Config config
var Config = config{
Auth: auth{
External: map[string]provider{
"apple": {},
"azure": {},
"bitbucket": {},
"discord": {},
"facebook": {},
"github": {},
"gitlab": {},
"google": {},
"keycloak": {},
"linkedin": {},
"notion": {},
"twitch": {},
"twitter": {},
"slack": {},
"spotify": {},
"workos": {},
"zoom": {},
},
JwtSecret: "super-secret-jwt-token-with-at-least-32-characters-long",
AnonKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0",
ServiceRoleKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU",
},
Db: db{
Password: "postgres",
},
Analytics: analytics{
ApiKey: "api-key",
},
}

// We follow these rules when adding new config:
// 1. Update init_config.toml with the new key, default value, and comments to explain usage.
// 2. Update config struct with new field and toml tag, written out in snake_case.
// 3. Add custom validations to LoadConfigFS function for the new field, such as range checks.
// 2. Update config struct with new field and toml tag (spelled in snake_case).
// 3. Add custom field validations to LoadConfigFS function for eg. integer range checks.
//
// If you are adding new user defined secrets, such as OAuth provider secret, the default value in
// init_config.toml should be an env var substitution. For example,
//
// > secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
//
// If you are adding an internal config or secret that doesn't need to be overridden by the user,
// exclude the field from toml serialization. For example,
//
// type auth struct {
// AnonKey string `toml:"-" mapstructure:"anon_key"`
// }
//
// Default values for internal configs should be added to `var Config` initialiser.
//
// If you are adding new secrets, such as API keys, use env var instead of toml. For example,
// 1. Config.Auth.AnonKey is tagged with `toml:"-" mapstructure:"anon_key"`. This tag prevents
// externalising to toml but allows reading from an env var named SUPABASE_AUTH_ANON_KEY.
// 2. Default values should be added to LoadConfigFS function, after checking for empty value.
// Use `mapstructure:"anon_key"` tag only if you want inject values from a predictable environment
// variable, such as SUPABASE_AUTH_ANON_KEY.
type (
config struct {
ProjectId string `toml:"project_id"`
Expand Down Expand Up @@ -273,12 +296,14 @@ func LoadConfigFS(fsys afero.Fs) error {
LogflareId = "supabase_analytics_" + Config.ProjectId
VectorId = "supabase_vector_" + Config.ProjectId
}
// Validate api config
if Config.Api.Port == 0 {
return errors.New("Missing required field in config: api.port")
}
// Append required schemas if they are missing
Config.Api.Schemas = removeDuplicates(append([]string{"public", "storage"}, Config.Api.Schemas...))
Config.Api.ExtraSearchPath = removeDuplicates(append([]string{"public"}, Config.Api.ExtraSearchPath...))
// Validate db config
if Config.Db.Port == 0 {
return errors.New("Missing required field in config: db.port")
}
Expand All @@ -299,28 +324,19 @@ func LoadConfigFS(fsys afero.Fs) error {
default:
return fmt.Errorf("Failed reading config: Invalid %s: %v.", Aqua("db.major_version"), Config.Db.MajorVersion)
}
if Config.Db.Password == "" {
Config.Db.Password = "postgres"
}
// Validate studio config
if Config.Studio.Port == 0 {
return errors.New("Missing required field in config: studio.port")
}
// Validate email config
if Config.Inbucket.Port == 0 {
return errors.New("Missing required field in config: inbucket.port")
}
// Validate auth config
if Config.Auth.SiteUrl == "" {
return errors.New("Missing required field in config: auth.site_url")
}
if Config.Auth.JwtSecret == "" {
Config.Auth.JwtSecret = "super-secret-jwt-token-with-at-least-32-characters-long"
}
if Config.Auth.AnonKey == "" {
Config.Auth.AnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"
}
if Config.Auth.ServiceRoleKey == "" {
Config.Auth.ServiceRoleKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"
}

// Validate sms config
if Config.Auth.Sms.Twilio.Enabled {
if len(Config.Auth.Sms.Twilio.AccountSid) == 0 {
return errors.New("Missing required field in config: auth.sms.twilio.account_sid")
Expand Down Expand Up @@ -359,96 +375,50 @@ func LoadConfigFS(fsys afero.Fs) error {
return errors.New("Missing required field in config: auth.sms.vonage.api_secret")
}
}

if Config.Auth.External == nil {
Config.Auth.External = map[string]provider{}
}
for _, ext := range authExternalProviders {
if _, ok := Config.Auth.External[ext]; !ok {
Config.Auth.External[ext] = provider{
Enabled: false,
ClientId: "",
Secret: "",
}
} else if Config.Auth.External[ext].Enabled {
var clientId, secret, redirectUri, url string

if Config.Auth.External[ext].ClientId == "" {
return fmt.Errorf("Missing required field in config: auth.external.%s.client_id", ext)
} else {
v, err := maybeLoadEnv(Config.Auth.External[ext].ClientId)
if err != nil {
return err
}
clientId = v
}
if Config.Auth.External[ext].Secret == "" {
return fmt.Errorf("Missing required field in config: auth.external.%s.secret", ext)
} else {
v, err := maybeLoadEnv(Config.Auth.External[ext].Secret)
if err != nil {
return err
}
secret = v
}
if Config.Auth.External[ext].RedirectUri != "" {
v, err := maybeLoadEnv(Config.Auth.External[ext].RedirectUri)
if err != nil {
return err
}
redirectUri = v
}
if Config.Auth.External[ext].Url != "" {
v, err := maybeLoadEnv(Config.Auth.External[ext].Url)
if err != nil {
return err
}
url = v
}

Config.Auth.External[ext] = provider{
Enabled: true,
ClientId: clientId,
Secret: secret,
RedirectUri: redirectUri,
Url: url,
}
// Validate oauth config
for ext, provider := range Config.Auth.External {
if !provider.Enabled {
continue
}
var err error
if provider.ClientId == "" {
return fmt.Errorf("Missing required field in config: auth.external.%s.client_id", ext)
}
if provider.Secret == "" {
return fmt.Errorf("Missing required field in config: auth.external.%s.secret", ext)
}
if provider.ClientId, err = maybeLoadEnv(provider.ClientId); err != nil {
return err
}
if provider.Secret, err = maybeLoadEnv(provider.Secret); err != nil {
return err
}
if provider.RedirectUri, err = maybeLoadEnv(provider.RedirectUri); err != nil {
return err
}
if provider.Url, err = maybeLoadEnv(provider.Url); err != nil {
return err
}
Config.Auth.External[ext] = provider
}
}

if Config.Functions == nil {
Config.Functions = map[string]function{}
}
// Validate functions config
for name, functionConfig := range Config.Functions {
verifyJWT := functionConfig.VerifyJWT

if verifyJWT == nil {
x := true
verifyJWT = &x
}

Config.Functions[name] = function{
VerifyJWT: verifyJWT,
ImportMap: functionConfig.ImportMap,
if functionConfig.VerifyJWT == nil {
verifyJWT := true
functionConfig.VerifyJWT = &verifyJWT
Config.Functions[name] = functionConfig
}
}

// Validate logflare config
if Config.Analytics.Enabled {
if len(Config.Analytics.GcpProjectId) == 0 {
return errors.New("Missing required field in config: analytics.gcp_project_id")
}
if len(Config.Analytics.GcpProjectNumber) == 0 {
return errors.New("Missing required field in config: analytics.gcp_project_number")
}
if len(Config.Analytics.GcpJwtPath) == 0 {
Config.Analytics.GcpJwtPath = "supabase/gcloud.json"
}
if len(Config.Analytics.ApiKey) == 0 {
Config.Analytics.ApiKey = "api-key"
}
}

return nil
}

Expand Down
5 changes: 2 additions & 3 deletions internal/utils/templates/init_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ enable_confirmations = false
enabled = false
account_sid = ""
message_service_sid = ""
# DO NOT commit your Twilio auth token to git. You can leave this field blank and define the
# following environment variable instead: SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN
auth_token = ""
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"

# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
Expand Down

0 comments on commit 477e76f

Please sign in to comment.