Skip to content

Commit 9ccb188

Browse files
Copilotavallete
andauthored
fix passkey config mapping for synced auth types
Agent-Logs-Url: https://github.com/supabase/cli/sessions/504b9e67-3e5a-48a3-ab42-dbee4864f2eb Co-authored-by: avallete <8771783+avallete@users.noreply.github.com>
1 parent f52aefe commit 9ccb188

5 files changed

Lines changed: 129 additions & 0 deletions

File tree

pkg/config/auth.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ type (
162162
PasswordRequirements PasswordRequirements `toml:"password_requirements" json:"password_requirements"`
163163
SigningKeysPath string `toml:"signing_keys_path" json:"signing_keys_path"`
164164
SigningKeys []JWK `toml:"-" json:"-"`
165+
Passkey *passkey `toml:"passkey" json:"passkey"`
165166

166167
RateLimit rateLimit `toml:"rate_limit" json:"rate_limit"`
167168
Captcha *captcha `toml:"captcha" json:"captcha"`
@@ -378,6 +379,13 @@ type (
378379
Ethereum ethereum `toml:"ethereum" json:"ethereum"`
379380
}
380381

382+
passkey struct {
383+
Enabled bool `toml:"enabled" json:"enabled"`
384+
RpDisplayName string `toml:"rp_display_name" json:"rp_display_name"`
385+
RpId string `toml:"rp_id" json:"rp_id"`
386+
RpOrigins []string `toml:"rp_origins" json:"rp_origins"`
387+
}
388+
381389
OAuthServer struct {
382390
Enabled bool `toml:"enabled" json:"enabled"`
383391
AllowDynamicRegistration bool `toml:"allow_dynamic_registration" json:"allow_dynamic_registration"`
@@ -407,6 +415,9 @@ func (a *auth) ToUpdateAuthConfigBody() v1API.UpdateAuthConfigBody {
407415
if a.Captcha != nil {
408416
a.Captcha.toAuthConfigBody(&body)
409417
}
418+
if a.Passkey != nil {
419+
a.Passkey.toAuthConfigBody(&body)
420+
}
410421
a.Hook.toAuthConfigBody(&body)
411422
a.MFA.toAuthConfigBody(&body)
412423
a.Sessions.toAuthConfigBody(&body)
@@ -430,6 +441,17 @@ func (a *auth) FromRemoteAuthConfig(remoteConfig v1API.AuthConfigResponse) {
430441
a.MinimumPasswordLength = cast.IntToUint(ValOrDefault(remoteConfig.PasswordMinLength, 0))
431442
prc := ValOrDefault(remoteConfig.PasswordRequiredCharacters, "")
432443
a.PasswordRequirements = NewPasswordRequirement(v1API.UpdateAuthConfigBodyPasswordRequiredCharacters(prc))
444+
passkeyDisplayName := ValOrDefault(remoteConfig.WebauthnRpDisplayName, "")
445+
passkeyId := ValOrDefault(remoteConfig.WebauthnRpId, "")
446+
passkeyOrigins := ValOrDefault(remoteConfig.WebauthnRpOrigins, "")
447+
if remoteConfig.PasskeyEnabled || passkeyDisplayName != "" || passkeyId != "" || passkeyOrigins != "" {
448+
if a.Passkey == nil {
449+
a.Passkey = &passkey{}
450+
}
451+
a.Passkey.fromAuthConfig(remoteConfig)
452+
} else {
453+
a.Passkey = nil
454+
}
433455
a.RateLimit.fromAuthConfig(remoteConfig)
434456
if s := a.Email.Smtp; s != nil && s.Enabled {
435457
a.RateLimit.EmailSent = cast.IntToUint(ValOrDefault(remoteConfig.RateLimitEmailSent, 0))
@@ -489,6 +511,20 @@ func (c *captcha) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) {
489511
c.Enabled = ValOrDefault(remoteConfig.SecurityCaptchaEnabled, false)
490512
}
491513

514+
func (p passkey) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) {
515+
body.PasskeyEnabled = cast.Ptr(p.Enabled)
516+
body.WebauthnRpDisplayName = nullable.NewNullableWithValue(p.RpDisplayName)
517+
body.WebauthnRpId = nullable.NewNullableWithValue(p.RpId)
518+
body.WebauthnRpOrigins = nullable.NewNullableWithValue(strings.Join(p.RpOrigins, ","))
519+
}
520+
521+
func (p *passkey) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) {
522+
p.Enabled = remoteConfig.PasskeyEnabled
523+
p.RpDisplayName = ValOrDefault(remoteConfig.WebauthnRpDisplayName, "")
524+
p.RpId = ValOrDefault(remoteConfig.WebauthnRpId, "")
525+
p.RpOrigins = strToArr(ValOrDefault(remoteConfig.WebauthnRpOrigins, ""))
526+
}
527+
492528
func (h hook) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) {
493529
// When local config is not set, we assume platform defaults should not change
494530
if hook := h.BeforeUserCreated; hook != nil {

pkg/config/auth_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,51 @@ func TestCaptchaDiff(t *testing.T) {
212212
})
213213
}
214214

215+
func TestPasskeyConfigMapping(t *testing.T) {
216+
t.Run("serializes passkey config to update body", func(t *testing.T) {
217+
c := newWithDefaults()
218+
c.Passkey = &passkey{
219+
Enabled: true,
220+
RpDisplayName: "Supabase CLI",
221+
RpId: "localhost",
222+
RpOrigins: []string{
223+
"http://127.0.0.1:3000",
224+
"https://localhost:3000",
225+
},
226+
}
227+
// Run test
228+
body := c.ToUpdateAuthConfigBody()
229+
// Check result
230+
if assert.NotNil(t, body.PasskeyEnabled) {
231+
assert.True(t, *body.PasskeyEnabled)
232+
}
233+
assert.Equal(t, "Supabase CLI", ValOrDefault(body.WebauthnRpDisplayName, ""))
234+
assert.Equal(t, "localhost", ValOrDefault(body.WebauthnRpId, ""))
235+
assert.Equal(t, "http://127.0.0.1:3000,https://localhost:3000", ValOrDefault(body.WebauthnRpOrigins, ""))
236+
})
237+
238+
t.Run("hydrates passkey config from remote", func(t *testing.T) {
239+
c := newWithDefaults()
240+
// Run test
241+
c.FromRemoteAuthConfig(v1API.AuthConfigResponse{
242+
PasskeyEnabled: true,
243+
WebauthnRpDisplayName: nullable.NewNullableWithValue("Supabase CLI"),
244+
WebauthnRpId: nullable.NewNullableWithValue("localhost"),
245+
WebauthnRpOrigins: nullable.NewNullableWithValue("http://127.0.0.1:3000,https://localhost:3000"),
246+
})
247+
// Check result
248+
if assert.NotNil(t, c.Passkey) {
249+
assert.True(t, c.Passkey.Enabled)
250+
assert.Equal(t, "Supabase CLI", c.Passkey.RpDisplayName)
251+
assert.Equal(t, "localhost", c.Passkey.RpId)
252+
assert.Equal(t, []string{
253+
"http://127.0.0.1:3000",
254+
"https://localhost:3000",
255+
}, c.Passkey.RpOrigins)
256+
}
257+
})
258+
}
259+
215260
func TestHookDiff(t *testing.T) {
216261
t.Run("local and remote enabled", func(t *testing.T) {
217262
c := newWithDefaults()

pkg/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ func (a *auth) Clone() auth {
260260
capt := *a.Captcha
261261
copy.Captcha = &capt
262262
}
263+
if copy.Passkey != nil {
264+
passkey := *a.Passkey
265+
passkey.RpOrigins = slices.Clone(a.Passkey.RpOrigins)
266+
copy.Passkey = &passkey
267+
}
263268
copy.External = maps.Clone(a.External)
264269
if a.Email.Smtp != nil {
265270
mailer := *a.Email.Smtp
@@ -916,6 +921,13 @@ func (c *config) Validate(fsys fs.FS) error {
916921
return errors.Errorf("failed to decode signing keys: %w", err)
917922
}
918923
}
924+
if c.Auth.Passkey != nil {
925+
for i, origin := range c.Auth.Passkey.RpOrigins {
926+
if err := assertEnvLoaded(origin); err != nil {
927+
return errors.Errorf("Invalid config for auth.passkey.rp_origins[%d]: %v", i, err)
928+
}
929+
}
930+
}
919931
if err := c.Auth.Hook.validate(); err != nil {
920932
return err
921933
}

pkg/config/config_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,35 @@ func TestConfigParsing(t *testing.T) {
7575
assert.Error(t, config.Load("", fsys))
7676
})
7777

78+
t.Run("config file with passkey settings", func(t *testing.T) {
79+
config := NewConfig()
80+
fsys := fs.MapFS{
81+
"supabase/config.toml": &fs.MapFile{Data: []byte(`
82+
[auth]
83+
enabled = true
84+
site_url = "http://127.0.0.1:3000"
85+
86+
[auth.passkey]
87+
enabled = true
88+
rp_display_name = "Supabase CLI"
89+
rp_id = "localhost"
90+
rp_origins = ["http://127.0.0.1:3000", "https://localhost:3000"]
91+
`)},
92+
}
93+
// Run test
94+
assert.NoError(t, config.Load("", fsys))
95+
// Check result
96+
if assert.NotNil(t, config.Auth.Passkey) {
97+
assert.True(t, config.Auth.Passkey.Enabled)
98+
assert.Equal(t, "Supabase CLI", config.Auth.Passkey.RpDisplayName)
99+
assert.Equal(t, "localhost", config.Auth.Passkey.RpId)
100+
assert.Equal(t, []string{
101+
"http://127.0.0.1:3000",
102+
"https://localhost:3000",
103+
}, config.Auth.Passkey.RpOrigins)
104+
}
105+
})
106+
78107
t.Run("parses experimental pgdelta config", func(t *testing.T) {
79108
config := NewConfig()
80109
fsys := fs.MapFS{

pkg/config/templates/config.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ minimum_password_length = 6
177177
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
178178
password_requirements = ""
179179

180+
# Configure passkey sign-ins.
181+
# [auth.passkey]
182+
# enabled = false
183+
# rp_display_name = "Supabase"
184+
# rp_id = "localhost"
185+
# rp_origins = ["http://127.0.0.1:3000"]
186+
180187
[auth.rate_limit]
181188
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
182189
email_sent = 2

0 commit comments

Comments
 (0)