Skip to content

Commit 63949ca

Browse files
authored
fix: source WebAuthn RP config from env vars (#2490)
Requires relying party configuration to be set in environment variables. - MFA WebAuthn challenge and verify no longer accept `rpId` / `rpOrigins` from the request body. - Validates `GOTRUE_WEBAUTHN_RP_*` at startup whenever MFA WebAuthn enroll or verify environment vars are enabled. `rpId` and `rpOrigins` under the `webauthn` object on `POST /factors/{factor_id}/challenge` and `POST /factors/{factor_id}/verify` are now silently ignored — they must no longer be sent in the request bodies. `GOTRUE_WEBAUTHN_RP_ID`, `GOTRUE_WEBAUTHN_RP_ORIGINS`, and `GOTRUE_WEBAUTHN_RP_DISPLAY_NAME` must be set if MFA WebAuthn enroll or verify are enabled.
1 parent c816cfe commit 63949ca

4 files changed

Lines changed: 17 additions & 73 deletions

File tree

internal/api/mfa.go

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import (
44
"bytes"
55
"crypto/subtle"
66
"encoding/json"
7-
"fmt"
87
"net/http"
98
"net/url"
10-
"strings"
119
"time"
1210

1311
"github.com/aaronarduino/goqrsvg"
@@ -77,8 +75,6 @@ type WebAuthnChallengeData struct {
7775
}
7876

7977
type WebAuthnParams struct {
80-
RPID string `json:"rpId,omitempty"`
81-
RPOrigins []string `json:"rpOrigins,omitempty"`
8278
Type string `json:"type"` // "create" or "request"
8379
CredentialResponse json.RawMessage `json:"credential_response"`
8480
}
@@ -87,39 +83,14 @@ type UnenrollFactorResponse struct {
8783
ID uuid.UUID `json:"id"`
8884
}
8985

90-
func (w *WebAuthnParams) ToConfig() (*webauthn.WebAuthn, error) {
91-
if w.RPID == "" {
92-
return nil, fmt.Errorf("webAuthn RP ID cannot be empty")
93-
}
94-
95-
if len(w.RPOrigins) == 0 {
96-
return nil, fmt.Errorf("webAuthn RP Origins cannot be empty")
97-
}
98-
99-
var validOrigins []string
100-
var invalidOrigins []string
101-
102-
for _, origin := range w.RPOrigins {
103-
parsedURL, err := url.Parse(origin)
104-
if err != nil || (parsedURL.Scheme != "https" && !(parsedURL.Scheme == "http" && parsedURL.Hostname() == "localhost")) || parsedURL.Host == "" {
105-
invalidOrigins = append(invalidOrigins, origin)
106-
} else {
107-
validOrigins = append(validOrigins, origin)
108-
}
109-
}
86+
func (a *API) getWebAuthnMFA() (*webauthn.WebAuthn, error) {
87+
rpConfig := a.config.WebAuthn
11088

111-
if len(invalidOrigins) > 0 {
112-
return nil, fmt.Errorf("invalid RP origins: %s", strings.Join(invalidOrigins, ", "))
113-
}
114-
115-
wconfig := &webauthn.Config{
116-
// DisplayName is optional in spec but required to be non-empty in libary, we use the RPID as a placeholder.
117-
RPDisplayName: w.RPID,
118-
RPID: w.RPID,
119-
RPOrigins: validOrigins,
120-
}
121-
122-
return webauthn.New(wconfig)
89+
return webauthn.New(&webauthn.Config{
90+
RPDisplayName: rpConfig.RPDisplayName,
91+
RPID: rpConfig.RPID,
92+
RPOrigins: rpConfig.RPOrigins,
93+
})
12394
}
12495

12596
const (
@@ -500,10 +471,7 @@ func (a *API) challengeWebAuthnFactor(w http.ResponseWriter, r *http.Request) er
500471
if err := retrieveRequestParams(r, params); err != nil {
501472
return err
502473
}
503-
if params.WebAuthn == nil {
504-
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "web_authn config required")
505-
}
506-
webAuthn, err := params.WebAuthn.ToConfig()
474+
webAuthn, err := a.getWebAuthnMFA()
507475
if err != nil {
508476
return err
509477
}
@@ -917,7 +885,7 @@ func (a *API) verifyWebAuthnFactor(w http.ResponseWriter, r *http.Request, param
917885
case params.WebAuthn.CredentialResponse == nil:
918886
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "credential_response required")
919887
default:
920-
webAuthn, err = params.WebAuthn.ToConfig()
888+
webAuthn, err = a.getWebAuthnMFA()
921889
if err != nil {
922890
return err
923891
}

internal/api/mfa_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ func (ts *MFATestSuite) SetupTest() {
9494

9595
ts.Config.MFA.WebAuthn.EnrollEnabled = true
9696
ts.Config.MFA.WebAuthn.VerifyEnabled = true
97+
ts.Config.WebAuthn = conf.WebAuthnConfiguration{
98+
RPID: "localhost",
99+
RPDisplayName: "Test App",
100+
RPOrigins: []string{"http://localhost:3000"},
101+
ChallengeExpiryDuration: 5 * time.Minute,
102+
}
97103

98104
key, err := totp.Generate(totp.GenerateOpts{
99105
Issuer: ts.TestDomain,
@@ -721,13 +727,9 @@ func (ts *MFATestSuite) TestMFAFollowedByPasswordSignIn() {
721727

722728
func (ts *MFATestSuite) TestChallengeWebAuthnFactor() {
723729
factor := models.NewWebAuthnFactor(ts.TestUser, "WebAuthnfactor")
724-
validWebAuthnConfiguration := &WebAuthnParams{
725-
RPID: "localhost",
726-
RPOrigins: []string{"http://localhost:3000"},
727-
}
728730
require.NoError(ts.T(), ts.API.db.Create(factor), "Error saving new test factor")
729731
token := ts.generateAAL1Token(ts.TestUser, &ts.TestSession.ID)
730-
w := performChallengeWebAuthnFlow(ts, factor.ID, token, validWebAuthnConfiguration)
732+
w := performChallengeWebAuthnFlow(ts, factor.ID, token, &WebAuthnParams{})
731733
require.Equal(ts.T(), http.StatusOK, w.Code)
732734
}
733735

internal/conf/configuration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,7 @@ func (c *GlobalConfiguration) Validate() error {
13331333
}
13341334
}
13351335

1336-
if c.Passkey.Enabled {
1336+
if c.Passkey.Enabled || c.MFA.WebAuthn.EnrollEnabled || c.MFA.WebAuthn.VerifyEnabled {
13371337
if err := c.WebAuthn.Validate(); err != nil {
13381338
return err
13391339
}

openapi.yaml

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,21 +1040,6 @@ paths:
10401040
enum:
10411041
- sms
10421042
- whatsapp
1043-
webauthn:
1044-
type: object
1045-
required:
1046-
- rpId
1047-
- rpOrigins
1048-
properties:
1049-
rpId:
1050-
type: string
1051-
description: The relying party identifier (usually the domain)
1052-
rpOrigins:
1053-
type: array
1054-
items:
1055-
type: string
1056-
minItems: 1
1057-
description: List of allowed origins for WebAuthn
10581043

10591044
responses:
10601045
200:
@@ -1103,20 +1088,9 @@ paths:
11031088
webauthn:
11041089
type: object
11051090
required:
1106-
- rpId
1107-
- rpOrigins
11081091
- type
11091092
- credential_response
11101093
properties:
1111-
rpId:
1112-
type: string
1113-
description: The relying party identifier
1114-
rpOrigins:
1115-
type: array
1116-
items:
1117-
type: string
1118-
minItems: 1
1119-
description: List of allowed origins for WebAuthn
11201094
type:
11211095
type: string
11221096
enum: [create, request]

0 commit comments

Comments
 (0)