Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve form field validation to make optional truly optional #426

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions recipe/emailpassword/api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ func validateFormFieldsOrThrowError(configFormFields []epmodels.NormalisedFormFi

func validateFormOrThrowError(configFormFields []epmodels.NormalisedFormField, inputs []epmodels.TypeFormField, tenantId string) error {
var validationErrors []errors.ErrorPayload
if len(configFormFields) != len(inputs) {
if len(configFormFields) < len(inputs) {
return supertokens.BadInputError{
Msg: "Are you sending too many / too few formFields?",
Msg: "Are you sending too many formFields?",
}
}
for _, field := range configFormFields {
Expand All @@ -105,16 +105,27 @@ func validateFormOrThrowError(configFormFields []epmodels.NormalisedFormField, i
break
}
}
if input.Value == "" && !field.Optional {

isValidInput := input.Value != ""

// If the field is not option and input is invalid, we should
// throw a validation error.
if !isValidInput && !field.Optional {
validationErrors = append(validationErrors, errors.ErrorPayload{ID: field.ID, ErrorMsg: "Field is not optional"})
} else {
err := field.Validate(input.Value, tenantId)
if err != nil {
validationErrors = append(validationErrors, errors.ErrorPayload{
ID: field.ID,
ErrorMsg: *err,
})
}
}

// If the input is invalid, we don't need to do anything
// as execution will reach here if field is optional.
if !isValidInput {
continue
}

err := field.Validate(input.Value, tenantId)
if err != nil {
validationErrors = append(validationErrors, errors.ErrorPayload{
ID: field.ID,
ErrorMsg: *err,
})
}
}
if len(validationErrors) != 0 {
Expand Down
205 changes: 190 additions & 15 deletions recipe/emailpassword/authFlow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1041,14 +1041,15 @@ func TestFormFieldsHasNoEmailField(t *testing.T) {

resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
assert.Equal(t, 200, resp.StatusCode)

err = json.Unmarshal(dataInBytes1, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))

assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "email", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
}

func TestFormFieldsHasNoPasswordField(t *testing.T) {
Expand Down Expand Up @@ -1130,12 +1131,14 @@ func TestFormFieldsHasNoPasswordField(t *testing.T) {

resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
assert.Equal(t, 200, resp.StatusCode)
err = json.Unmarshal(dataInBytes1, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "password", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))

}

Expand Down Expand Up @@ -2343,14 +2346,15 @@ func TestFormFieldsAddedInConfigButNotInInputToSignupCheckErrorAboutItBeingMissi
t.Error(err.Error())
}
res.Body.Close()
assert.Equal(t, 400, res.StatusCode)
assert.Equal(t, 200, res.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))

assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "testField", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
}

func TestBadCaseInputWithoutOtional(t *testing.T) {
Expand Down Expand Up @@ -2441,6 +2445,86 @@ func TestBadCaseInputWithoutOtional(t *testing.T) {

}

func TestOptionalInputFieldDoesNotThrowError(t *testing.T) {
optionalVal := true
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
APIDomain: "api.supertokens.io",
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&epmodels.TypeInput{
SignUpFeature: &epmodels.TypeInputSignUp{
FormFields: []epmodels.TypeInputFormField{
{
ID: "testField2",
Optional: &optionalVal,
},
},
},
}),
session.Init(&sessmodels.TypeInput{
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
return sessmodels.CookieTransferMethod
},
}),
},
}

BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()
err := supertokens.Init(configValue)
if err != nil {
t.Error(err.Error())
}
mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

formFields := map[string][]map[string]string{
"formFields": {
{
"id": "password",
"value": "validpass123",
},
{
"id": "email",
"value": "[email protected]",
},
},
}

postBody, err := json.Marshal(formFields)
if err != nil {
t.Error(err.Error())
}

resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", bytes.NewBuffer(postBody))

if err != nil {
t.Error(err.Error())
}

dataInBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Error(err.Error())
}
resp.Body.Close()

assert.Equal(t, 200, resp.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "OK", data["status"].(string))
}

func TestGoodCaseInputWithOtional(t *testing.T) {
optionalVal := true
configValue := supertokens.TypeInput{
Expand Down Expand Up @@ -2587,14 +2671,15 @@ func TestInputFormFieldWithoutEmailField(t *testing.T) {
}
resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
assert.Equal(t, 200, resp.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))

assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "email", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
}

func TestInputFormFieldWithoutPasswordField(t *testing.T) {
Expand Down Expand Up @@ -2654,13 +2739,15 @@ func TestInputFormFieldWithoutPasswordField(t *testing.T) {
}
resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
assert.Equal(t, 200, resp.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "password", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
}

func TestInputFormFieldHasADifferentNumberOfCustomFiledsThanInConfigFormFields(t *testing.T) {
Expand Down Expand Up @@ -2741,13 +2828,15 @@ func TestInputFormFieldHasADifferentNumberOfCustomFiledsThanInConfigFormFields(t
}
resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
assert.Equal(t, 200, resp.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
assert.Equal(t, "testField2", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))

}

Expand Down Expand Up @@ -3177,3 +3266,89 @@ func TestSignUpAPIWorksWhenInputIsFine(t *testing.T) {
assert.Equal(t, "OK", result["status"])
assert.Equal(t, "[email protected]", result["user"].(map[string]interface{})["email"])
}

rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
func TestInputFormFieldHasMoreNumberOfCustomFiledsThanInConfigFormFields(t *testing.T) {
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
APIDomain: "api.supertokens.io",
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&epmodels.TypeInput{
SignUpFeature: &epmodels.TypeInputSignUp{
FormFields: []epmodels.TypeInputFormField{
{
ID: "testField2",
},
},
},
}),
session.Init(&sessmodels.TypeInput{
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
return sessmodels.CookieTransferMethod
},
}),
},
}

BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()
err := supertokens.Init(configValue)
if err != nil {
t.Error(err.Error())
}
mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

formFields := map[string][]map[string]string{
"formFields": {
{
"id": "password",
"value": "validpass123",
},
{
"id": "email",
"value": "[email protected]",
},
{
"id": "testField",
"value": "",
},
{
"id": "testField2",
"value": "",
},
},
}

postBody, err := json.Marshal(formFields)
if err != nil {
t.Error(err.Error())
}

resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", bytes.NewBuffer(postBody))

if err != nil {
t.Error(err.Error())
}

dataInBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Error(err.Error())
}
resp.Body.Close()

assert.Equal(t, 400, resp.StatusCode)
var data map[string]interface{}
err = json.Unmarshal(dataInBytes, &data)
if err != nil {
t.Error(err.Error())
}
assert.Equal(t, "Are you sending too many formFields?", data["message"].(string))
}
Loading