From 2bc9fc5eb4177e109ee58f2f368ecbe6fee4a2c6 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Fri, 10 Apr 2026 19:40:23 +0100 Subject: [PATCH 01/15] feat: creating auth access templates - Creating different templates for 'functions new' command - Handling '--auth' flag to specify which template use --- cmd/functions.go | 15 ++++- internal/functions/new/new.go | 65 ++++++++++++++----- internal/functions/new/templates/config.toml | 10 +-- internal/functions/new/templates/deno.json | 3 +- .../new/templates/index_always_access.ts | 32 +++++++++ .../new/templates/index_apikey_access.ts | 46 +++++++++++++ .../{index.ts => index_user_access.ts} | 26 ++++---- 7 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 internal/functions/new/templates/index_always_access.ts create mode 100644 internal/functions/new/templates/index_apikey_access.ts rename internal/functions/new/templates/{index.ts => index_user_access.ts} (57%) diff --git a/cmd/functions.go b/cmd/functions.go index bbf99bb9a8..5f66dc6579 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -85,6 +85,13 @@ var ( }, } + authAccessMode = utils.EnumFlag{ + Allowed: []string{ + string(new_.AuthAccessModeAlways), + string(new_.AuthAccessModeApiKey), + string(new_.AuthAccessModeUser), + }, + } functionsNewCmd = &cobra.Command{ Use: "new ", Short: "Create a new Function locally", @@ -94,7 +101,12 @@ var ( return cmd.Root().PersistentPreRunE(cmd, args) }, RunE: func(cmd *cobra.Command, args []string) error { - return new_.Run(cmd.Context(), args[0], afero.NewOsFs()) + authMode := new_.AuthAccessModeAlways + if len(authAccessMode.Value) > 0 { + authMode = new_.AuthAccessMode(authAccessMode.Value) + } + + return new_.Run(cmd.Context(), args[0], authMode, afero.NewOsFs()) }, } @@ -172,6 +184,7 @@ func init() { functionsDownloadCmd.MarkFlagsMutuallyExclusive("use-api", "use-docker", "legacy-bundle") cobra.CheckErr(downloadFlags.MarkHidden("legacy-bundle")) cobra.CheckErr(downloadFlags.MarkHidden("use-docker")) + functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access (default always)") functionsCmd.AddCommand(functionsListCmd) functionsCmd.AddCommand(functionsDeleteCmd) functionsCmd.AddCommand(functionsDeployCmd) diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index 1b0f307912..e6045a956d 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -16,9 +16,22 @@ import ( "github.com/supabase/cli/internal/utils/flags" ) +type AuthAccessMode string + +const ( + AuthAccessModeAlways AuthAccessMode = "always" + AuthAccessModeApiKey AuthAccessMode = "apikey" + AuthAccessModeUser AuthAccessMode = "user" +) + var ( - //go:embed templates/index.ts - indexEmbed string + //go:embed templates/index_always_access.ts + indexAuthAlwaysEmbed string + //go:embed templates/index_apikey_access.ts + indexAuthApiKeyEmbed string + //go:embed templates/index_user_access.ts + indexAuthUserEmbed string + //go:embed templates/deno.json denoEmbed string //go:embed templates/.npmrc @@ -26,16 +39,26 @@ var ( //go:embed templates/config.toml configEmbed string - indexTemplate = template.Must(template.New("index").Parse(indexEmbed)) + indexAuthTemplates = map[AuthAccessMode]*template.Template{ + AuthAccessModeAlways: template.Must(template.New("index").Parse(indexAuthAlwaysEmbed)), + AuthAccessModeApiKey: template.Must(template.New("index").Parse(indexAuthApiKeyEmbed)), + AuthAccessModeUser: template.Must(template.New("index").Parse(indexAuthUserEmbed)), + } + configTemplate = template.Must(template.New("config").Parse(configEmbed)) ) type indexConfig struct { - URL string - Token string + URL string + PublishableKey string +} + +type functionConfig struct { + Slug string + VerifyJWT bool } -func Run(ctx context.Context, slug string, fsys afero.Fs) error { +func Run(ctx context.Context, slug string, authMode AuthAccessMode, fsys afero.Fs) error { // 1. Sanity checks. if err := utils.ValidateFunctionSlug(slug); err != nil { return err @@ -56,17 +79,18 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error { if err := flags.LoadConfig(fsys); err != nil { fmt.Fprintln(utils.GetDebugLogger(), err) } - if err := createEntrypointFile(slug, fsys); err != nil { + if err := createEntrypointFile(slug, authMode, fsys); err != nil { return err } - if err := appendConfigFile(slug, fsys); err != nil { + verifyJWT := authMode == AuthAccessModeUser + if err := appendConfigFile(slug, verifyJWT, fsys); err != nil { return err } // 3. Create optional files - if err := afero.WriteFile(fsys, filepath.Join(funcDir, "deno.json"), []byte(denoEmbed), 0644); err != nil { + if err := afero.WriteFile(fsys, filepath.Join(funcDir, "deno.json"), []byte(denoEmbed), 0o644); err != nil { return errors.Errorf("failed to create deno.json config: %w", err) } - if err := afero.WriteFile(fsys, filepath.Join(funcDir, ".npmrc"), []byte(npmrcEmbed), 0644); err != nil { + if err := afero.WriteFile(fsys, filepath.Join(funcDir, ".npmrc"), []byte(npmrcEmbed), 0o644); err != nil { return errors.Errorf("failed to create .npmrc config: %w", err) } fmt.Println("Created new Function at " + utils.Bold(funcDir)) @@ -79,33 +103,40 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error { return nil } -func createEntrypointFile(slug string, fsys afero.Fs) error { +func createEntrypointFile(slug string, authMode AuthAccessMode, fsys afero.Fs) error { entrypointPath := filepath.Join(utils.FunctionsDir, slug, "index.ts") - f, err := fsys.OpenFile(entrypointPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) + f, err := fsys.OpenFile(entrypointPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) if err != nil { return errors.Errorf("failed to create entrypoint: %w", err) } defer f.Close() + indexTemplate, hasTemplate := indexAuthTemplates[authMode] + if !hasTemplate { + return errors.Errorf("failed to write entrypoint: '%w' is not a valid template", authMode) + } if err := indexTemplate.Option("missingkey=error").Execute(f, indexConfig{ - URL: utils.GetApiUrl("/functions/v1/" + slug), - Token: utils.Config.Auth.AnonKey.Value, + URL: utils.GetApiUrl("/functions/v1/" + slug), + PublishableKey: utils.Config.Auth.PublishableKey.Value, }); err != nil { return errors.Errorf("failed to write entrypoint: %w", err) } return nil } -func appendConfigFile(slug string, fsys afero.Fs) error { +func appendConfigFile(slug string, verifyJWT bool, fsys afero.Fs) error { if _, exists := utils.Config.Functions[slug]; exists { fmt.Fprintf(os.Stderr, "[functions.%s] is already declared in %s\n", slug, utils.Bold(utils.ConfigPath)) return nil } - f, err := fsys.OpenFile(utils.ConfigPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + f, err := fsys.OpenFile(utils.ConfigPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { return errors.Errorf("failed to append config: %w", err) } defer f.Close() - if err := configTemplate.Option("missingkey=error").Execute(f, slug); err != nil { + if err := configTemplate.Option("missingkey=error").Execute(f, functionConfig{ + Slug: slug, + VerifyJWT: verifyJWT, + }); err != nil { return errors.Errorf("failed to append template: %w", err) } return nil diff --git a/internal/functions/new/templates/config.toml b/internal/functions/new/templates/config.toml index 9b3b197098..30f63a70ee 100644 --- a/internal/functions/new/templates/config.toml +++ b/internal/functions/new/templates/config.toml @@ -1,11 +1,11 @@ -[functions.{{ . }}] +[functions.{{ .Slug }}] enabled = true -verify_jwt = true -import_map = "./functions/{{ . }}/deno.json" +verify_jwt = {{ .VerifyJWT }} +import_map = "./functions/{{ .Slug }}/deno.json" # Uncomment to specify a custom file path to the entrypoint. # Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx -entrypoint = "./functions/{{ . }}/index.ts" +entrypoint = "./functions/{{ .Slug }}/index.ts" # Specifies static files to be bundled with the function. Supports glob patterns. # For example, if you want to serve static HTML pages in your function: -# static_files = [ "./functions/{{ . }}/*.html" ] +# static_files = [ "./functions/{{ .Slug }}/*.html" ] diff --git a/internal/functions/new/templates/deno.json b/internal/functions/new/templates/deno.json index 758d0703d1..40fd1e8b28 100644 --- a/internal/functions/new/templates/deno.json +++ b/internal/functions/new/templates/deno.json @@ -1,5 +1,6 @@ { "imports": { - "@supabase/functions-js": "jsr:@supabase/functions-js@^2" + "@supabase/functions-js": "jsr:@supabase/functions-js@^2", + "@supabase/server": "npm:@supabase/server" } } diff --git a/internal/functions/new/templates/index_always_access.ts b/internal/functions/new/templates/index_always_access.ts new file mode 100644 index 0000000000..5f47542408 --- /dev/null +++ b/internal/functions/new/templates/index_always_access.ts @@ -0,0 +1,32 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +// Setup type definitions for built-in Supabase Runtime APIs +import "@supabase/functions-js/edge-runtime.d.ts"; +import { withSupabase } from "@supabase/server"; + +console.log("Hello from Functions!"); + +// This endpoint uses 'always' access, no credentials required, every request is accepted. +// Use it for health checks, public APIs, or when you handle auth yourself inside the handler. +export default { + fetch: withSupabase({ allow: "always" }, async (req, ctx) => { + const { name } = await req.json(); + + return Response.json({ + message: `Hello ${name}!`, + }); + }), +}; + +/* To invoke locally: + + 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) + 2. Make an HTTP request: + + curl -i --location --request POST '{{ .URL }}' \ + --header 'Content-Type: application/json' \ + --data '{"name":"Functions"}' + +*/ diff --git a/internal/functions/new/templates/index_apikey_access.ts b/internal/functions/new/templates/index_apikey_access.ts new file mode 100644 index 0000000000..cfb9acc91d --- /dev/null +++ b/internal/functions/new/templates/index_apikey_access.ts @@ -0,0 +1,46 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +// Setup type definitions for built-in Supabase Runtime APIs +import "@supabase/functions-js/edge-runtime.d.ts"; +import { withSupabase } from "@supabase/server"; + +console.log("Hello from Functions!"); + +// This endpoint uses 'public' | 'secret' access, apiKey is required. +// Use public for Client-facing, key-validated endpoints +// Use secret for Server-to-server, internal calls +export default { + fetch: withSupabase({ allow: ["public", "secret"] }, async (req, ctx) => { + // Called by another service with a secret key + // ctx.supabaseAdmin bypasses RLS — use for privileged operations + /* + if (ctx.authType === "secret") { + const { user_id } = await req.json(); + const { data } = await ctx.supabaseAdmin.auth.admin.getUserById(user_id); + + return Response.json({ + email: data?.user?.email, + }); + } + */ + + const { name } = await req.json(); + + return Response.json({ + message: `Hello ${name}!`, + }); + }), +}; + +/* To invoke locally: + + 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) + 2. Make an HTTP request: + + curl -i --location --request POST '{{ .URL }}' \ + --header 'apiKey: {{ .PublishableKey }}' \ + --data '{"name":"Functions"}' + +*/ diff --git a/internal/functions/new/templates/index.ts b/internal/functions/new/templates/index_user_access.ts similarity index 57% rename from internal/functions/new/templates/index.ts rename to internal/functions/new/templates/index_user_access.ts index dd1f7ce676..0e0c71c4aa 100644 --- a/internal/functions/new/templates/index.ts +++ b/internal/functions/new/templates/index_user_access.ts @@ -4,20 +4,20 @@ // Setup type definitions for built-in Supabase Runtime APIs import "@supabase/functions-js/edge-runtime.d.ts" +import { withSupabase } from '@supabase/server' console.log("Hello from Functions!") -Deno.serve(async (req) => { - const { name } = await req.json() - const data = { - message: `Hello ${name}!`, - } +// This endpoint uses 'user' access, credentials is required. +export default { + fetch: withSupabase({ allow: 'user' }, async (_req, ctx) => { + const email = ctx.userClaims?.email; - return new Response( - JSON.stringify(data), - { headers: { "Content-Type": "application/json" } }, - ) -}) + return Response.json({ + message: `Hello ${email}!`, + }) + }), +} /* To invoke locally: @@ -25,8 +25,6 @@ Deno.serve(async (req) => { 2. Make an HTTP request: curl -i --location --request POST '{{ .URL }}' \ - --header 'Authorization: Bearer {{ .Token }}' \ - --header 'Content-Type: application/json' \ - --data '{"name":"Functions"}' - + --header 'apiKey: {{ .PublishableKey }}' + --header 'Authorization: Bearer ' */ From 7a81831165228bdb919179c8ea39a489042159cf Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Fri, 10 Apr 2026 19:55:59 +0100 Subject: [PATCH 02/15] test: fix tests --- internal/functions/new/new_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/functions/new/new_test.go b/internal/functions/new/new_test.go index df082d0d05..3c395913f1 100644 --- a/internal/functions/new/new_test.go +++ b/internal/functions/new/new_test.go @@ -16,7 +16,7 @@ func TestNewCommand(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Run test - assert.NoError(t, Run(context.Background(), "test-func", fsys)) + assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") content, err := afero.ReadFile(fsys, funcPath) @@ -41,22 +41,22 @@ func TestNewCommand(t *testing.T) { }) t.Run("throws error on malformed slug", func(t *testing.T) { - assert.Error(t, Run(context.Background(), "@", afero.NewMemMapFs())) + assert.Error(t, Run(context.Background(), "@", AuthAccessModeAlways, afero.NewMemMapFs())) }) t.Run("throws error on duplicate slug", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") - require.NoError(t, afero.WriteFile(fsys, funcPath, []byte{}, 0644)) + require.NoError(t, afero.WriteFile(fsys, funcPath, []byte{}, 0o644)) // Run test - assert.Error(t, Run(context.Background(), "test-func", fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) }) t.Run("throws error on permission denied", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) // Run test - assert.Error(t, Run(context.Background(), "test-func", fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) }) } From 86227121fc970b20b0dd7d450cacc64d39a4a6f5 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Fri, 10 Apr 2026 20:19:04 +0100 Subject: [PATCH 03/15] fix: wrong format type --- internal/functions/new/new.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index e6045a956d..6d1c189076 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -112,7 +112,7 @@ func createEntrypointFile(slug string, authMode AuthAccessMode, fsys afero.Fs) e defer f.Close() indexTemplate, hasTemplate := indexAuthTemplates[authMode] if !hasTemplate { - return errors.Errorf("failed to write entrypoint: '%w' is not a valid template", authMode) + return errors.Errorf("failed to write entrypoint: '%v' is not a valid template", authMode) } if err := indexTemplate.Option("missingkey=error").Execute(f, indexConfig{ URL: utils.GetApiUrl("/functions/v1/" + slug), From 12944645e48b95d2573dde4d9913514d6969e939 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Fri, 10 Apr 2026 20:38:56 +0100 Subject: [PATCH 04/15] test: adding tests for auth templates --- internal/functions/new/new_test.go | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/internal/functions/new/new_test.go b/internal/functions/new/new_test.go index 3c395913f1..22e4e72b49 100644 --- a/internal/functions/new/new_test.go +++ b/internal/functions/new/new_test.go @@ -2,6 +2,7 @@ package new import ( "context" + "fmt" "path/filepath" "testing" @@ -26,8 +27,11 @@ func TestNewCommand(t *testing.T) { ) // Verify config.toml is updated - _, err = afero.ReadFile(fsys, utils.ConfigPath) + content, err = afero.ReadFile(fsys, utils.ConfigPath) assert.NoError(t, err, "config.toml should be created") + assert.Contains(t, string(content), "[functions.test-func]") + // Always access mode should not verify jwt + assert.Contains(t, string(content), "verify_jwt = false") // Verify deno.json exists denoPath := filepath.Join(utils.FunctionsDir, "test-func", "deno.json") @@ -40,6 +44,37 @@ func TestNewCommand(t *testing.T) { assert.NoError(t, err, ".npmrc should be created") }) + t.Run("creates new function with apikey access", func(t *testing.T) { + fsys := afero.NewMemMapFs() + assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeApiKey, fsys)) + + // Validate output + funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") + content, _ := afero.ReadFile(fsys, funcPath) + // Should contain the PublishableKey as example + assert.Contains(t, string(content), fmt.Sprintf("--header 'apiKey: %v'", utils.Config.Auth.PublishableKey.Value)) + + // Verify config.toml is updated to not verify jwt + content, _ = afero.ReadFile(fsys, utils.ConfigPath) + assert.Contains(t, string(content), "verify_jwt = false") + }) + + t.Run("creates new function with user access", func(t *testing.T) { + fsys := afero.NewMemMapFs() + assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeUser, fsys)) + + // Validate output + funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") + content, _ := afero.ReadFile(fsys, funcPath) + // Should contain the PublishableKey as example as well placeholder for UserToken + assert.Contains(t, string(content), fmt.Sprintf("--header 'apiKey: %v'", utils.Config.Auth.PublishableKey.Value)) + assert.Contains(t, string(content), "--header 'Authorization: Bearer '") + + // Verify config.toml is updated and verify jwt enabled + content, _ = afero.ReadFile(fsys, utils.ConfigPath) + assert.Contains(t, string(content), "verify_jwt = true") + }) + t.Run("throws error on malformed slug", func(t *testing.T) { assert.Error(t, Run(context.Background(), "@", AuthAccessModeAlways, afero.NewMemMapFs())) }) From 0008c370373f7c6d28de9ad47e85feb753d53abf Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 14 Apr 2026 00:15:41 +0100 Subject: [PATCH 05/15] fix: improving 'always' template description --- internal/functions/new/templates/index_always_access.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/functions/new/templates/index_always_access.ts b/internal/functions/new/templates/index_always_access.ts index 5f47542408..61d63a4310 100644 --- a/internal/functions/new/templates/index_always_access.ts +++ b/internal/functions/new/templates/index_always_access.ts @@ -9,7 +9,7 @@ import { withSupabase } from "@supabase/server"; console.log("Hello from Functions!"); // This endpoint uses 'always' access, no credentials required, every request is accepted. -// Use it for health checks, public APIs, or when you handle auth yourself inside the handler. +// Use it for health checks, public APIs, or when you need to implement your own auth logic. export default { fetch: withSupabase({ allow: "always" }, async (req, ctx) => { const { name } = await req.json(); From 5d85cffd9a0f8c3bc56ba51bdecdc996ced65091 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 14 Apr 2026 16:31:49 +0100 Subject: [PATCH 06/15] stamp: changing default function template to 'apikey' --- cmd/functions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/functions.go b/cmd/functions.go index 5f66dc6579..a620404184 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -101,7 +101,7 @@ var ( return cmd.Root().PersistentPreRunE(cmd, args) }, RunE: func(cmd *cobra.Command, args []string) error { - authMode := new_.AuthAccessModeAlways + authMode := new_.AuthAccessModeApiKey if len(authAccessMode.Value) > 0 { authMode = new_.AuthAccessMode(authAccessMode.Value) } @@ -184,7 +184,7 @@ func init() { functionsDownloadCmd.MarkFlagsMutuallyExclusive("use-api", "use-docker", "legacy-bundle") cobra.CheckErr(downloadFlags.MarkHidden("legacy-bundle")) cobra.CheckErr(downloadFlags.MarkHidden("use-docker")) - functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access (default always)") + functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access (default apikey)") functionsCmd.AddCommand(functionsListCmd) functionsCmd.AddCommand(functionsDeleteCmd) functionsCmd.AddCommand(functionsDeployCmd) From aca125dad6a692165bace3b952d61833b32b0429 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Wed, 29 Apr 2026 20:00:43 +0100 Subject: [PATCH 07/15] fix: using default value from 'EnumFlag' util --- cmd/functions.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cmd/functions.go b/cmd/functions.go index a620404184..903b32a666 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -91,6 +91,7 @@ var ( string(new_.AuthAccessModeApiKey), string(new_.AuthAccessModeUser), }, + Value: string(new_.AuthAccessModeApiKey), } functionsNewCmd = &cobra.Command{ Use: "new ", @@ -101,10 +102,7 @@ var ( return cmd.Root().PersistentPreRunE(cmd, args) }, RunE: func(cmd *cobra.Command, args []string) error { - authMode := new_.AuthAccessModeApiKey - if len(authAccessMode.Value) > 0 { - authMode = new_.AuthAccessMode(authAccessMode.Value) - } + authMode := new_.AuthAccessMode(authAccessMode.Value) return new_.Run(cmd.Context(), args[0], authMode, afero.NewOsFs()) }, @@ -184,7 +182,7 @@ func init() { functionsDownloadCmd.MarkFlagsMutuallyExclusive("use-api", "use-docker", "legacy-bundle") cobra.CheckErr(downloadFlags.MarkHidden("legacy-bundle")) cobra.CheckErr(downloadFlags.MarkHidden("use-docker")) - functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access (default apikey)") + functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access") functionsCmd.AddCommand(functionsListCmd) functionsCmd.AddCommand(functionsDeleteCmd) functionsCmd.AddCommand(functionsDeployCmd) From 170e362c35f3705b186b220a464555aa392f2f44 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Wed, 29 Apr 2026 20:03:39 +0100 Subject: [PATCH 08/15] stamp: format --- internal/functions/new/templates/index_user_access.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/functions/new/templates/index_user_access.ts b/internal/functions/new/templates/index_user_access.ts index 0e0c71c4aa..ba8b298c53 100644 --- a/internal/functions/new/templates/index_user_access.ts +++ b/internal/functions/new/templates/index_user_access.ts @@ -4,13 +4,13 @@ // Setup type definitions for built-in Supabase Runtime APIs import "@supabase/functions-js/edge-runtime.d.ts" -import { withSupabase } from '@supabase/server' +import { withSupabase } from "@supabase/server" console.log("Hello from Functions!") // This endpoint uses 'user' access, credentials is required. export default { - fetch: withSupabase({ allow: 'user' }, async (_req, ctx) => { + fetch: withSupabase({ allow: "user" }, async (_req, ctx) => { const email = ctx.userClaims?.email; return Response.json({ From 65e10ed8879d13bca35eb1ad640789f0cb04e883 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Wed, 29 Apr 2026 20:04:41 +0100 Subject: [PATCH 09/15] fix: adding missing backslash on invoke command --- internal/functions/new/templates/index_user_access.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/functions/new/templates/index_user_access.ts b/internal/functions/new/templates/index_user_access.ts index ba8b298c53..eb23a112d1 100644 --- a/internal/functions/new/templates/index_user_access.ts +++ b/internal/functions/new/templates/index_user_access.ts @@ -25,6 +25,6 @@ export default { 2. Make an HTTP request: curl -i --location --request POST '{{ .URL }}' \ - --header 'apiKey: {{ .PublishableKey }}' + --header 'apiKey: {{ .PublishableKey }}' \ --header 'Authorization: Bearer ' */ From 9d8c4fd2a17a2d892b7781245ff481971cf3dfa0 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 18:11:59 +0100 Subject: [PATCH 10/15] fix: use new server sdk API syntax --- cmd/functions.go | 2 +- internal/functions/new/new.go | 8 ++++---- internal/functions/new/new_test.go | 8 ++++---- internal/functions/new/templates/index_apikey_access.ts | 6 +++--- .../{index_always_access.ts => index_no_auth_access.ts} | 2 +- internal/functions/new/templates/index_user_access.ts | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) rename internal/functions/new/templates/{index_always_access.ts => index_no_auth_access.ts} (93%) diff --git a/cmd/functions.go b/cmd/functions.go index 903b32a666..16d8237e37 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -87,7 +87,7 @@ var ( authAccessMode = utils.EnumFlag{ Allowed: []string{ - string(new_.AuthAccessModeAlways), + string(new_.AuthAccessModeNone), string(new_.AuthAccessModeApiKey), string(new_.AuthAccessModeUser), }, diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index 6d1c189076..c1f3357a8d 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -19,14 +19,14 @@ import ( type AuthAccessMode string const ( - AuthAccessModeAlways AuthAccessMode = "always" + AuthAccessModeNone AuthAccessMode = "none" AuthAccessModeApiKey AuthAccessMode = "apikey" AuthAccessModeUser AuthAccessMode = "user" ) var ( - //go:embed templates/index_always_access.ts - indexAuthAlwaysEmbed string + //go:embed templates/index_no_auth_access.ts + indexAuthNoneEmbed string //go:embed templates/index_apikey_access.ts indexAuthApiKeyEmbed string //go:embed templates/index_user_access.ts @@ -40,7 +40,7 @@ var ( configEmbed string indexAuthTemplates = map[AuthAccessMode]*template.Template{ - AuthAccessModeAlways: template.Must(template.New("index").Parse(indexAuthAlwaysEmbed)), + AuthAccessModeNone: template.Must(template.New("index").Parse(indexAuthNoneEmbed)), AuthAccessModeApiKey: template.Must(template.New("index").Parse(indexAuthApiKeyEmbed)), AuthAccessModeUser: template.Must(template.New("index").Parse(indexAuthUserEmbed)), } diff --git a/internal/functions/new/new_test.go b/internal/functions/new/new_test.go index 22e4e72b49..aed2bad246 100644 --- a/internal/functions/new/new_test.go +++ b/internal/functions/new/new_test.go @@ -17,7 +17,7 @@ func TestNewCommand(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Run test - assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) + assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") content, err := afero.ReadFile(fsys, funcPath) @@ -76,7 +76,7 @@ func TestNewCommand(t *testing.T) { }) t.Run("throws error on malformed slug", func(t *testing.T) { - assert.Error(t, Run(context.Background(), "@", AuthAccessModeAlways, afero.NewMemMapFs())) + assert.Error(t, Run(context.Background(), "@", AuthAccessModeNone, afero.NewMemMapFs())) }) t.Run("throws error on duplicate slug", func(t *testing.T) { @@ -85,13 +85,13 @@ func TestNewCommand(t *testing.T) { funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") require.NoError(t, afero.WriteFile(fsys, funcPath, []byte{}, 0o644)) // Run test - assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) }) t.Run("throws error on permission denied", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) // Run test - assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeAlways, fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) }) } diff --git a/internal/functions/new/templates/index_apikey_access.ts b/internal/functions/new/templates/index_apikey_access.ts index cfb9acc91d..4f9aa1f430 100644 --- a/internal/functions/new/templates/index_apikey_access.ts +++ b/internal/functions/new/templates/index_apikey_access.ts @@ -8,11 +8,11 @@ import { withSupabase } from "@supabase/server"; console.log("Hello from Functions!"); -// This endpoint uses 'public' | 'secret' access, apiKey is required. -// Use public for Client-facing, key-validated endpoints +// This endpoint uses 'publishable' | 'secret' access, apiKey is required. +// Use publishable for Client-facing, key-validated endpoints // Use secret for Server-to-server, internal calls export default { - fetch: withSupabase({ allow: ["public", "secret"] }, async (req, ctx) => { + fetch: withSupabase({ auth: ["publishable", "secret"] }, async (req, ctx) => { // Called by another service with a secret key // ctx.supabaseAdmin bypasses RLS — use for privileged operations /* diff --git a/internal/functions/new/templates/index_always_access.ts b/internal/functions/new/templates/index_no_auth_access.ts similarity index 93% rename from internal/functions/new/templates/index_always_access.ts rename to internal/functions/new/templates/index_no_auth_access.ts index 61d63a4310..85c0581cde 100644 --- a/internal/functions/new/templates/index_always_access.ts +++ b/internal/functions/new/templates/index_no_auth_access.ts @@ -11,7 +11,7 @@ console.log("Hello from Functions!"); // This endpoint uses 'always' access, no credentials required, every request is accepted. // Use it for health checks, public APIs, or when you need to implement your own auth logic. export default { - fetch: withSupabase({ allow: "always" }, async (req, ctx) => { + fetch: withSupabase({ auth: "none" }, async (req, ctx) => { const { name } = await req.json(); return Response.json({ diff --git a/internal/functions/new/templates/index_user_access.ts b/internal/functions/new/templates/index_user_access.ts index eb23a112d1..2851fe3b8d 100644 --- a/internal/functions/new/templates/index_user_access.ts +++ b/internal/functions/new/templates/index_user_access.ts @@ -10,7 +10,7 @@ console.log("Hello from Functions!") // This endpoint uses 'user' access, credentials is required. export default { - fetch: withSupabase({ allow: "user" }, async (_req, ctx) => { + fetch: withSupabase({ auth: "user" }, async (_req, ctx) => { const email = ctx.userClaims?.email; return Response.json({ From 733155bade73a890fe25fd28911fa7a641c10fae Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 18:14:13 +0100 Subject: [PATCH 11/15] stamp: pin '@supabase/server' to v1 --- internal/functions/new/templates/deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/functions/new/templates/deno.json b/internal/functions/new/templates/deno.json index 40fd1e8b28..db206e8f46 100644 --- a/internal/functions/new/templates/deno.json +++ b/internal/functions/new/templates/deno.json @@ -1,6 +1,6 @@ { "imports": { "@supabase/functions-js": "jsr:@supabase/functions-js@^2", - "@supabase/server": "npm:@supabase/server" + "@supabase/server": "npm:@supabase/server@^1" } } From 32f9b4cfb54252d7abfbec6685d642da96b02d45 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 18:17:22 +0100 Subject: [PATCH 12/15] stamp: codegen --- pkg/api/types.gen.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 91b2727ae9..f3ee2d4c09 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -1392,6 +1392,7 @@ const ( V1ListEntitlementsResponseEntitlementsFeatureKeyAssistantAdvanceModel V1ListEntitlementsResponseEntitlementsFeatureKey = "assistant.advance_model" V1ListEntitlementsResponseEntitlementsFeatureKeyAuthAdvancedAuthSettings V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.advanced_auth_settings" V1ListEntitlementsResponseEntitlementsFeatureKeyAuthCustomJwtTemplate V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.custom_jwt_template" + V1ListEntitlementsResponseEntitlementsFeatureKeyAuthCustomOauthMaxProviders V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.custom_oauth.max_providers" V1ListEntitlementsResponseEntitlementsFeatureKeyAuthHooks V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.hooks" V1ListEntitlementsResponseEntitlementsFeatureKeyAuthLeakedPasswordProtection V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.leaked_password_protection" V1ListEntitlementsResponseEntitlementsFeatureKeyAuthMfaEnhancedSecurity V1ListEntitlementsResponseEntitlementsFeatureKey = "auth.mfa_enhanced_security" From 59f0197d7ec0ed5dce9bc32a6b08163fbf6b660b Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 21:20:48 +0100 Subject: [PATCH 13/15] stamp: renaming 'AuthAccessMode*' to 'AuthMode' terminology --- cmd/functions.go | 14 +++++++------- internal/functions/new/new.go | 22 +++++++++++----------- internal/functions/new/new_test.go | 12 ++++++------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cmd/functions.go b/cmd/functions.go index 16d8237e37..50aa1f2bab 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -85,13 +85,13 @@ var ( }, } - authAccessMode = utils.EnumFlag{ + authMode = utils.EnumFlag{ Allowed: []string{ - string(new_.AuthAccessModeNone), - string(new_.AuthAccessModeApiKey), - string(new_.AuthAccessModeUser), + string(new_.AuthModeNone), + string(new_.AuthModeApiKey), + string(new_.AuthModeUser), }, - Value: string(new_.AuthAccessModeApiKey), + Value: string(new_.AuthModeApiKey), } functionsNewCmd = &cobra.Command{ Use: "new ", @@ -102,7 +102,7 @@ var ( return cmd.Root().PersistentPreRunE(cmd, args) }, RunE: func(cmd *cobra.Command, args []string) error { - authMode := new_.AuthAccessMode(authAccessMode.Value) + authMode := new_.AuthMode(authMode.Value) return new_.Run(cmd.Context(), args[0], authMode, afero.NewOsFs()) }, @@ -182,7 +182,7 @@ func init() { functionsDownloadCmd.MarkFlagsMutuallyExclusive("use-api", "use-docker", "legacy-bundle") cobra.CheckErr(downloadFlags.MarkHidden("legacy-bundle")) cobra.CheckErr(downloadFlags.MarkHidden("use-docker")) - functionsNewCmd.Flags().Var(&authAccessMode, "auth", "use a specific auth access") + functionsNewCmd.Flags().Var(&authMode, "auth", "use a specific auth mode") functionsCmd.AddCommand(functionsListCmd) functionsCmd.AddCommand(functionsDeleteCmd) functionsCmd.AddCommand(functionsDeployCmd) diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index c1f3357a8d..8af96cf2ac 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -16,12 +16,12 @@ import ( "github.com/supabase/cli/internal/utils/flags" ) -type AuthAccessMode string +type AuthMode string const ( - AuthAccessModeNone AuthAccessMode = "none" - AuthAccessModeApiKey AuthAccessMode = "apikey" - AuthAccessModeUser AuthAccessMode = "user" + AuthModeNone AuthMode = "none" + AuthModeApiKey AuthMode = "apikey" + AuthModeUser AuthMode = "user" ) var ( @@ -39,10 +39,10 @@ var ( //go:embed templates/config.toml configEmbed string - indexAuthTemplates = map[AuthAccessMode]*template.Template{ - AuthAccessModeNone: template.Must(template.New("index").Parse(indexAuthNoneEmbed)), - AuthAccessModeApiKey: template.Must(template.New("index").Parse(indexAuthApiKeyEmbed)), - AuthAccessModeUser: template.Must(template.New("index").Parse(indexAuthUserEmbed)), + indexAuthTemplates = map[AuthMode]*template.Template{ + AuthModeNone: template.Must(template.New("index").Parse(indexAuthNoneEmbed)), + AuthModeApiKey: template.Must(template.New("index").Parse(indexAuthApiKeyEmbed)), + AuthModeUser: template.Must(template.New("index").Parse(indexAuthUserEmbed)), } configTemplate = template.Must(template.New("config").Parse(configEmbed)) @@ -58,7 +58,7 @@ type functionConfig struct { VerifyJWT bool } -func Run(ctx context.Context, slug string, authMode AuthAccessMode, fsys afero.Fs) error { +func Run(ctx context.Context, slug string, authMode AuthMode, fsys afero.Fs) error { // 1. Sanity checks. if err := utils.ValidateFunctionSlug(slug); err != nil { return err @@ -82,7 +82,7 @@ func Run(ctx context.Context, slug string, authMode AuthAccessMode, fsys afero.F if err := createEntrypointFile(slug, authMode, fsys); err != nil { return err } - verifyJWT := authMode == AuthAccessModeUser + verifyJWT := authMode == AuthModeUser if err := appendConfigFile(slug, verifyJWT, fsys); err != nil { return err } @@ -103,7 +103,7 @@ func Run(ctx context.Context, slug string, authMode AuthAccessMode, fsys afero.F return nil } -func createEntrypointFile(slug string, authMode AuthAccessMode, fsys afero.Fs) error { +func createEntrypointFile(slug string, authMode AuthMode, fsys afero.Fs) error { entrypointPath := filepath.Join(utils.FunctionsDir, slug, "index.ts") f, err := fsys.OpenFile(entrypointPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) if err != nil { diff --git a/internal/functions/new/new_test.go b/internal/functions/new/new_test.go index aed2bad246..e86a691181 100644 --- a/internal/functions/new/new_test.go +++ b/internal/functions/new/new_test.go @@ -17,7 +17,7 @@ func TestNewCommand(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Run test - assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) + assert.NoError(t, Run(context.Background(), "test-func", AuthModeNone, fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") content, err := afero.ReadFile(fsys, funcPath) @@ -46,7 +46,7 @@ func TestNewCommand(t *testing.T) { t.Run("creates new function with apikey access", func(t *testing.T) { fsys := afero.NewMemMapFs() - assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeApiKey, fsys)) + assert.NoError(t, Run(context.Background(), "test-func", AuthModeApiKey, fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") @@ -61,7 +61,7 @@ func TestNewCommand(t *testing.T) { t.Run("creates new function with user access", func(t *testing.T) { fsys := afero.NewMemMapFs() - assert.NoError(t, Run(context.Background(), "test-func", AuthAccessModeUser, fsys)) + assert.NoError(t, Run(context.Background(), "test-func", AuthModeUser, fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") @@ -76,7 +76,7 @@ func TestNewCommand(t *testing.T) { }) t.Run("throws error on malformed slug", func(t *testing.T) { - assert.Error(t, Run(context.Background(), "@", AuthAccessModeNone, afero.NewMemMapFs())) + assert.Error(t, Run(context.Background(), "@", AuthModeNone, afero.NewMemMapFs())) }) t.Run("throws error on duplicate slug", func(t *testing.T) { @@ -85,13 +85,13 @@ func TestNewCommand(t *testing.T) { funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") require.NoError(t, afero.WriteFile(fsys, funcPath, []byte{}, 0o644)) // Run test - assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthModeNone, fsys)) }) t.Run("throws error on permission denied", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) // Run test - assert.Error(t, Run(context.Background(), "test-func", AuthAccessModeNone, fsys)) + assert.Error(t, Run(context.Background(), "test-func", AuthModeNone, fsys)) }) } From 6941356f726d15e355a81b0bc3944d2b3cf18bab Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 21:23:37 +0100 Subject: [PATCH 14/15] stamp: renaming template files to match 'AuthMode' terminology --- internal/functions/new/new.go | 18 +++++++++--------- ...key_access.ts => index_auth_mode_apikey.ts} | 0 ..._auth_access.ts => index_auth_mode_none.ts} | 0 ..._user_access.ts => index_auth_mode_user.ts} | 0 4 files changed, 9 insertions(+), 9 deletions(-) rename internal/functions/new/templates/{index_apikey_access.ts => index_auth_mode_apikey.ts} (100%) rename internal/functions/new/templates/{index_no_auth_access.ts => index_auth_mode_none.ts} (100%) rename internal/functions/new/templates/{index_user_access.ts => index_auth_mode_user.ts} (100%) diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index 8af96cf2ac..46e13d4cb0 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -25,12 +25,12 @@ const ( ) var ( - //go:embed templates/index_no_auth_access.ts - indexAuthNoneEmbed string - //go:embed templates/index_apikey_access.ts - indexAuthApiKeyEmbed string - //go:embed templates/index_user_access.ts - indexAuthUserEmbed string + //go:embed templates/index_auth_mode_none.ts + indexAuthModeNoneEmbed string + //go:embed templates/index_auth_mode_apikey.ts + indexAuthModeApiKeyEmbed string + //go:embed templates/index_auth_mode_user.ts + indexAuthModeUserEmbed string //go:embed templates/deno.json denoEmbed string @@ -40,9 +40,9 @@ var ( configEmbed string indexAuthTemplates = map[AuthMode]*template.Template{ - AuthModeNone: template.Must(template.New("index").Parse(indexAuthNoneEmbed)), - AuthModeApiKey: template.Must(template.New("index").Parse(indexAuthApiKeyEmbed)), - AuthModeUser: template.Must(template.New("index").Parse(indexAuthUserEmbed)), + AuthModeNone: template.Must(template.New("index").Parse(indexAuthModeNoneEmbed)), + AuthModeApiKey: template.Must(template.New("index").Parse(indexAuthModeApiKeyEmbed)), + AuthModeUser: template.Must(template.New("index").Parse(indexAuthModeUserEmbed)), } configTemplate = template.Must(template.New("config").Parse(configEmbed)) diff --git a/internal/functions/new/templates/index_apikey_access.ts b/internal/functions/new/templates/index_auth_mode_apikey.ts similarity index 100% rename from internal/functions/new/templates/index_apikey_access.ts rename to internal/functions/new/templates/index_auth_mode_apikey.ts diff --git a/internal/functions/new/templates/index_no_auth_access.ts b/internal/functions/new/templates/index_auth_mode_none.ts similarity index 100% rename from internal/functions/new/templates/index_no_auth_access.ts rename to internal/functions/new/templates/index_auth_mode_none.ts diff --git a/internal/functions/new/templates/index_user_access.ts b/internal/functions/new/templates/index_auth_mode_user.ts similarity index 100% rename from internal/functions/new/templates/index_user_access.ts rename to internal/functions/new/templates/index_auth_mode_user.ts From 382befc5f32556d91b1dd79bf0a9a55edd35b900 Mon Sep 17 00:00:00 2001 From: Kalleby Santos Date: Tue, 5 May 2026 21:28:23 +0100 Subject: [PATCH 15/15] fix: template comment --- internal/functions/new/templates/index_auth_mode_none.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/functions/new/templates/index_auth_mode_none.ts b/internal/functions/new/templates/index_auth_mode_none.ts index 85c0581cde..a9c8e6e93e 100644 --- a/internal/functions/new/templates/index_auth_mode_none.ts +++ b/internal/functions/new/templates/index_auth_mode_none.ts @@ -8,7 +8,7 @@ import { withSupabase } from "@supabase/server"; console.log("Hello from Functions!"); -// This endpoint uses 'always' access, no credentials required, every request is accepted. +// This endpoint uses auth 'none', no credentials required, every request is accepted. // Use it for health checks, public APIs, or when you need to implement your own auth logic. export default { fetch: withSupabase({ auth: "none" }, async (req, ctx) => {