4444 //go:embed templates/initial_schemas/15.sql
4545 InitialSchemaPg15Sql string
4646
47- authExternalProviders = []string {
48- "apple" ,
49- "azure" ,
50- "bitbucket" ,
51- "discord" ,
52- "facebook" ,
53- "github" ,
54- "gitlab" ,
55- "google" ,
56- "keycloak" ,
57- "linkedin" ,
58- "notion" ,
59- "twitch" ,
60- "twitter" ,
61- "slack" ,
62- "spotify" ,
63- "workos" ,
64- "zoom" ,
65- }
66-
6747 //go:embed templates/init_config.toml
6848 initConfigEmbed string
6949 initConfigTemplate = template .Must (template .New ("initConfig" ).Parse (initConfigEmbed ))
@@ -82,17 +62,60 @@ func (s *sizeInBytes) UnmarshalText(text []byte) error {
8262 return err
8363}
8464
85- var Config config
65+ var Config = config {
66+ Auth : auth {
67+ External : map [string ]provider {
68+ "apple" : {},
69+ "azure" : {},
70+ "bitbucket" : {},
71+ "discord" : {},
72+ "facebook" : {},
73+ "github" : {},
74+ "gitlab" : {},
75+ "google" : {},
76+ "keycloak" : {},
77+ "linkedin" : {},
78+ "notion" : {},
79+ "twitch" : {},
80+ "twitter" : {},
81+ "slack" : {},
82+ "spotify" : {},
83+ "workos" : {},
84+ "zoom" : {},
85+ },
86+ JwtSecret : "super-secret-jwt-token-with-at-least-32-characters-long" ,
87+ AnonKey : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0" ,
88+ ServiceRoleKey : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU" ,
89+ },
90+ Db : db {
91+ Password : "postgres" ,
92+ },
93+ Analytics : analytics {
94+ ApiKey : "api-key" ,
95+ },
96+ }
8697
8798// We follow these rules when adding new config:
8899// 1. Update init_config.toml with the new key, default value, and comments to explain usage.
89- // 2. Update config struct with new field and toml tag, written out in snake_case.
90- // 3. Add custom validations to LoadConfigFS function for the new field, such as range checks.
100+ // 2. Update config struct with new field and toml tag (spelled in snake_case).
101+ // 3. Add custom field validations to LoadConfigFS function for eg. integer range checks.
102+ //
103+ // If you are adding new user defined secrets, such as OAuth provider secret, the default value in
104+ // init_config.toml should be an env var substitution. For example,
105+ //
106+ // > secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
91107//
92- // If you are adding new secrets, such as API keys, use env var instead of toml. For example,
93- // 1. Config.Auth.AnonKey is tagged with `toml:"-" mapstructure:"anon_key"`. This tag prevents
94- // externalising to toml but allows reading from an env var named SUPABASE_AUTH_ANON_KEY.
95- // 2. Default values should be added to LoadConfigFS function, after checking for empty value.
108+ // If you are adding an internal config or secret that doesn't need to be overridden by the user,
109+ // exclude the field from toml serialization. For example,
110+ //
111+ // type auth struct {
112+ // AnonKey string `toml:"-" mapstructure:"anon_key"`
113+ // }
114+ //
115+ // Use `mapstructure:"anon_key"` tag only if you want inject values from a predictable environment
116+ // variable, such as SUPABASE_AUTH_ANON_KEY.
117+ //
118+ // Default values for internal configs should be added to `var Config` initialiser.
96119type (
97120 config struct {
98121 ProjectId string `toml:"project_id"`
@@ -273,12 +296,14 @@ func LoadConfigFS(fsys afero.Fs) error {
273296 LogflareId = "supabase_analytics_" + Config .ProjectId
274297 VectorId = "supabase_vector_" + Config .ProjectId
275298 }
299+ // Validate api config
276300 if Config .Api .Port == 0 {
277301 return errors .New ("Missing required field in config: api.port" )
278302 }
279303 // Append required schemas if they are missing
280304 Config .Api .Schemas = removeDuplicates (append ([]string {"public" , "storage" }, Config .Api .Schemas ... ))
281305 Config .Api .ExtraSearchPath = removeDuplicates (append ([]string {"public" }, Config .Api .ExtraSearchPath ... ))
306+ // Validate db config
282307 if Config .Db .Port == 0 {
283308 return errors .New ("Missing required field in config: db.port" )
284309 }
@@ -299,28 +324,20 @@ func LoadConfigFS(fsys afero.Fs) error {
299324 default :
300325 return fmt .Errorf ("Failed reading config: Invalid %s: %v." , Aqua ("db.major_version" ), Config .Db .MajorVersion )
301326 }
302- if Config .Db .Password == "" {
303- Config .Db .Password = "postgres"
304- }
327+ // Validate studio config
305328 if Config .Studio .Port == 0 {
306329 return errors .New ("Missing required field in config: studio.port" )
307330 }
331+ // Validate email config
308332 if Config .Inbucket .Port == 0 {
309333 return errors .New ("Missing required field in config: inbucket.port" )
310334 }
335+ // Validate auth config
311336 if Config .Auth .SiteUrl == "" {
312337 return errors .New ("Missing required field in config: auth.site_url" )
313338 }
314- if Config .Auth .JwtSecret == "" {
315- Config .Auth .JwtSecret = "super-secret-jwt-token-with-at-least-32-characters-long"
316- }
317- if Config .Auth .AnonKey == "" {
318- Config .Auth .AnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"
319- }
320- if Config .Auth .ServiceRoleKey == "" {
321- Config .Auth .ServiceRoleKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"
322- }
323-
339+ // Validate sms config
340+ var err error
324341 if Config .Auth .Sms .Twilio .Enabled {
325342 if len (Config .Auth .Sms .Twilio .AccountSid ) == 0 {
326343 return errors .New ("Missing required field in config: auth.sms.twilio.account_sid" )
@@ -331,6 +348,9 @@ func LoadConfigFS(fsys afero.Fs) error {
331348 if len (Config .Auth .Sms .Twilio .AuthToken ) == 0 {
332349 return errors .New ("Missing required field in config: auth.sms.twilio.auth_token" )
333350 }
351+ if Config .Auth .Sms .Twilio .AuthToken , err = maybeLoadEnv (Config .Auth .Sms .Twilio .AuthToken ); err != nil {
352+ return err
353+ }
334354 }
335355 if Config .Auth .Sms .Messagebird .Enabled {
336356 if len (Config .Auth .Sms .Messagebird .Originator ) == 0 {
@@ -339,6 +359,9 @@ func LoadConfigFS(fsys afero.Fs) error {
339359 if len (Config .Auth .Sms .Messagebird .AccessKey ) == 0 {
340360 return errors .New ("Missing required field in config: auth.sms.messagebird.access_key" )
341361 }
362+ if Config .Auth .Sms .Messagebird .AccessKey , err = maybeLoadEnv (Config .Auth .Sms .Messagebird .AccessKey ); err != nil {
363+ return err
364+ }
342365 }
343366 if Config .Auth .Sms .Textlocal .Enabled {
344367 if len (Config .Auth .Sms .Textlocal .Sender ) == 0 {
@@ -347,6 +370,9 @@ func LoadConfigFS(fsys afero.Fs) error {
347370 if len (Config .Auth .Sms .Textlocal .ApiKey ) == 0 {
348371 return errors .New ("Missing required field in config: auth.sms.textlocal.api_key" )
349372 }
373+ if Config .Auth .Sms .Textlocal .ApiKey , err = maybeLoadEnv (Config .Auth .Sms .Textlocal .ApiKey ); err != nil {
374+ return err
375+ }
350376 }
351377 if Config .Auth .Sms .Vonage .Enabled {
352378 if len (Config .Auth .Sms .Vonage .From ) == 0 {
@@ -358,97 +384,56 @@ func LoadConfigFS(fsys afero.Fs) error {
358384 if len (Config .Auth .Sms .Vonage .ApiSecret ) == 0 {
359385 return errors .New ("Missing required field in config: auth.sms.vonage.api_secret" )
360386 }
387+ if Config .Auth .Sms .Vonage .ApiKey , err = maybeLoadEnv (Config .Auth .Sms .Vonage .ApiKey ); err != nil {
388+ return err
389+ }
390+ if Config .Auth .Sms .Vonage .ApiSecret , err = maybeLoadEnv (Config .Auth .Sms .Vonage .ApiSecret ); err != nil {
391+ return err
392+ }
361393 }
362-
363- if Config .Auth .External == nil {
364- Config .Auth .External = map [string ]provider {}
365- }
366- for _ , ext := range authExternalProviders {
367- if _ , ok := Config .Auth .External [ext ]; ! ok {
368- Config .Auth .External [ext ] = provider {
369- Enabled : false ,
370- ClientId : "" ,
371- Secret : "" ,
372- }
373- } else if Config .Auth .External [ext ].Enabled {
374- var clientId , secret , redirectUri , url string
375-
376- if Config .Auth .External [ext ].ClientId == "" {
377- return fmt .Errorf ("Missing required field in config: auth.external.%s.client_id" , ext )
378- } else {
379- v , err := maybeLoadEnv (Config .Auth .External [ext ].ClientId )
380- if err != nil {
381- return err
382- }
383- clientId = v
384- }
385- if Config .Auth .External [ext ].Secret == "" {
386- return fmt .Errorf ("Missing required field in config: auth.external.%s.secret" , ext )
387- } else {
388- v , err := maybeLoadEnv (Config .Auth .External [ext ].Secret )
389- if err != nil {
390- return err
391- }
392- secret = v
393- }
394- if Config .Auth .External [ext ].RedirectUri != "" {
395- v , err := maybeLoadEnv (Config .Auth .External [ext ].RedirectUri )
396- if err != nil {
397- return err
398- }
399- redirectUri = v
400- }
401- if Config .Auth .External [ext ].Url != "" {
402- v , err := maybeLoadEnv (Config .Auth .External [ext ].Url )
403- if err != nil {
404- return err
405- }
406- url = v
407- }
408-
409- Config .Auth .External [ext ] = provider {
410- Enabled : true ,
411- ClientId : clientId ,
412- Secret : secret ,
413- RedirectUri : redirectUri ,
414- Url : url ,
415- }
394+ // Validate oauth config
395+ for ext , provider := range Config .Auth .External {
396+ if ! provider .Enabled {
397+ continue
398+ }
399+ if provider .ClientId == "" {
400+ return fmt .Errorf ("Missing required field in config: auth.external.%s.client_id" , ext )
401+ }
402+ if provider .Secret == "" {
403+ return fmt .Errorf ("Missing required field in config: auth.external.%s.secret" , ext )
404+ }
405+ if provider .ClientId , err = maybeLoadEnv (provider .ClientId ); err != nil {
406+ return err
416407 }
408+ if provider .Secret , err = maybeLoadEnv (provider .Secret ); err != nil {
409+ return err
410+ }
411+ if provider .RedirectUri , err = maybeLoadEnv (provider .RedirectUri ); err != nil {
412+ return err
413+ }
414+ if provider .Url , err = maybeLoadEnv (provider .Url ); err != nil {
415+ return err
416+ }
417+ Config .Auth .External [ext ] = provider
417418 }
418419 }
419-
420- if Config .Functions == nil {
421- Config .Functions = map [string ]function {}
422- }
420+ // Validate functions config
423421 for name , functionConfig := range Config .Functions {
424- verifyJWT := functionConfig .VerifyJWT
425-
426- if verifyJWT == nil {
427- x := true
428- verifyJWT = & x
429- }
430-
431- Config .Functions [name ] = function {
432- VerifyJWT : verifyJWT ,
433- ImportMap : functionConfig .ImportMap ,
422+ if functionConfig .VerifyJWT == nil {
423+ verifyJWT := true
424+ functionConfig .VerifyJWT = & verifyJWT
425+ Config .Functions [name ] = functionConfig
434426 }
435427 }
436-
428+ // Validate logflare config
437429 if Config .Analytics .Enabled {
438430 if len (Config .Analytics .GcpProjectId ) == 0 {
439431 return errors .New ("Missing required field in config: analytics.gcp_project_id" )
440432 }
441433 if len (Config .Analytics .GcpProjectNumber ) == 0 {
442434 return errors .New ("Missing required field in config: analytics.gcp_project_number" )
443435 }
444- if len (Config .Analytics .GcpJwtPath ) == 0 {
445- Config .Analytics .GcpJwtPath = "supabase/gcloud.json"
446- }
447- if len (Config .Analytics .ApiKey ) == 0 {
448- Config .Analytics .ApiKey = "api-key"
449- }
450436 }
451-
452437 return nil
453438}
454439
0 commit comments