Skip to content

Commit 6e12269

Browse files
Merge pull request #426 from deepjyoti30Alt/feat/form-valiadation-improvements-for-proper-optional-field
Improve form field validation to make optional truly optional
2 parents f2c10d0 + ad1bdf0 commit 6e12269

File tree

2 files changed

+212
-26
lines changed

2 files changed

+212
-26
lines changed

recipe/emailpassword/api/utils.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ func validateFormFieldsOrThrowError(configFormFields []epmodels.NormalisedFormFi
9292

9393
func validateFormOrThrowError(configFormFields []epmodels.NormalisedFormField, inputs []epmodels.TypeFormField, tenantId string) error {
9494
var validationErrors []errors.ErrorPayload
95-
if len(configFormFields) != len(inputs) {
95+
if len(configFormFields) < len(inputs) {
9696
return supertokens.BadInputError{
97-
Msg: "Are you sending too many / too few formFields?",
97+
Msg: "Are you sending too many formFields?",
9898
}
9999
}
100100
for _, field := range configFormFields {
@@ -105,16 +105,27 @@ func validateFormOrThrowError(configFormFields []epmodels.NormalisedFormField, i
105105
break
106106
}
107107
}
108-
if input.Value == "" && !field.Optional {
108+
109+
isValidInput := input.Value != ""
110+
111+
// If the field is not option and input is invalid, we should
112+
// throw a validation error.
113+
if !isValidInput && !field.Optional {
109114
validationErrors = append(validationErrors, errors.ErrorPayload{ID: field.ID, ErrorMsg: "Field is not optional"})
110-
} else {
111-
err := field.Validate(input.Value, tenantId)
112-
if err != nil {
113-
validationErrors = append(validationErrors, errors.ErrorPayload{
114-
ID: field.ID,
115-
ErrorMsg: *err,
116-
})
117-
}
115+
}
116+
117+
// If the input is invalid, we don't need to do anything
118+
// as execution will reach here if field is optional.
119+
if !isValidInput {
120+
continue
121+
}
122+
123+
err := field.Validate(input.Value, tenantId)
124+
if err != nil {
125+
validationErrors = append(validationErrors, errors.ErrorPayload{
126+
ID: field.ID,
127+
ErrorMsg: *err,
128+
})
118129
}
119130
}
120131
if len(validationErrors) != 0 {

recipe/emailpassword/authFlow_test.go

Lines changed: 190 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,14 +1041,15 @@ func TestFormFieldsHasNoEmailField(t *testing.T) {
10411041

10421042
resp.Body.Close()
10431043

1044-
assert.Equal(t, 400, resp.StatusCode)
1044+
assert.Equal(t, 200, resp.StatusCode)
10451045

10461046
err = json.Unmarshal(dataInBytes1, &data)
10471047
if err != nil {
10481048
t.Error(err.Error())
10491049
}
1050-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
1051-
1050+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
1051+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
1052+
assert.Equal(t, "email", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
10521053
}
10531054

10541055
func TestFormFieldsHasNoPasswordField(t *testing.T) {
@@ -1130,12 +1131,14 @@ func TestFormFieldsHasNoPasswordField(t *testing.T) {
11301131

11311132
resp.Body.Close()
11321133

1133-
assert.Equal(t, 400, resp.StatusCode)
1134+
assert.Equal(t, 200, resp.StatusCode)
11341135
err = json.Unmarshal(dataInBytes1, &data)
11351136
if err != nil {
11361137
t.Error(err.Error())
11371138
}
1138-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
1139+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
1140+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
1141+
assert.Equal(t, "password", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
11391142

11401143
}
11411144

@@ -2343,14 +2346,15 @@ func TestFormFieldsAddedInConfigButNotInInputToSignupCheckErrorAboutItBeingMissi
23432346
t.Error(err.Error())
23442347
}
23452348
res.Body.Close()
2346-
assert.Equal(t, 400, res.StatusCode)
2349+
assert.Equal(t, 200, res.StatusCode)
23472350
var data map[string]interface{}
23482351
err = json.Unmarshal(dataInBytes, &data)
23492352
if err != nil {
23502353
t.Error(err.Error())
23512354
}
2352-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
2353-
2355+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
2356+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
2357+
assert.Equal(t, "testField", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
23542358
}
23552359

23562360
func TestBadCaseInputWithoutOtional(t *testing.T) {
@@ -2441,6 +2445,86 @@ func TestBadCaseInputWithoutOtional(t *testing.T) {
24412445

24422446
}
24432447

2448+
func TestOptionalInputFieldDoesNotThrowError(t *testing.T) {
2449+
optionalVal := true
2450+
configValue := supertokens.TypeInput{
2451+
Supertokens: &supertokens.ConnectionInfo{
2452+
ConnectionURI: "http://localhost:8080",
2453+
},
2454+
AppInfo: supertokens.AppInfo{
2455+
APIDomain: "api.supertokens.io",
2456+
AppName: "SuperTokens",
2457+
WebsiteDomain: "supertokens.io",
2458+
},
2459+
RecipeList: []supertokens.Recipe{
2460+
Init(&epmodels.TypeInput{
2461+
SignUpFeature: &epmodels.TypeInputSignUp{
2462+
FormFields: []epmodels.TypeInputFormField{
2463+
{
2464+
ID: "testField2",
2465+
Optional: &optionalVal,
2466+
},
2467+
},
2468+
},
2469+
}),
2470+
session.Init(&sessmodels.TypeInput{
2471+
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
2472+
return sessmodels.CookieTransferMethod
2473+
},
2474+
}),
2475+
},
2476+
}
2477+
2478+
BeforeEach()
2479+
unittesting.StartUpST("localhost", "8080")
2480+
defer AfterEach()
2481+
err := supertokens.Init(configValue)
2482+
if err != nil {
2483+
t.Error(err.Error())
2484+
}
2485+
mux := http.NewServeMux()
2486+
testServer := httptest.NewServer(supertokens.Middleware(mux))
2487+
defer testServer.Close()
2488+
2489+
formFields := map[string][]map[string]string{
2490+
"formFields": {
2491+
{
2492+
"id": "password",
2493+
"value": "validpass123",
2494+
},
2495+
{
2496+
"id": "email",
2497+
"value": "[email protected]",
2498+
},
2499+
},
2500+
}
2501+
2502+
postBody, err := json.Marshal(formFields)
2503+
if err != nil {
2504+
t.Error(err.Error())
2505+
}
2506+
2507+
resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", bytes.NewBuffer(postBody))
2508+
2509+
if err != nil {
2510+
t.Error(err.Error())
2511+
}
2512+
2513+
dataInBytes, err := io.ReadAll(resp.Body)
2514+
if err != nil {
2515+
t.Error(err.Error())
2516+
}
2517+
resp.Body.Close()
2518+
2519+
assert.Equal(t, 200, resp.StatusCode)
2520+
var data map[string]interface{}
2521+
err = json.Unmarshal(dataInBytes, &data)
2522+
if err != nil {
2523+
t.Error(err.Error())
2524+
}
2525+
assert.Equal(t, "OK", data["status"].(string))
2526+
}
2527+
24442528
func TestGoodCaseInputWithOtional(t *testing.T) {
24452529
optionalVal := true
24462530
configValue := supertokens.TypeInput{
@@ -2587,14 +2671,15 @@ func TestInputFormFieldWithoutEmailField(t *testing.T) {
25872671
}
25882672
resp.Body.Close()
25892673

2590-
assert.Equal(t, 400, resp.StatusCode)
2674+
assert.Equal(t, 200, resp.StatusCode)
25912675
var data map[string]interface{}
25922676
err = json.Unmarshal(dataInBytes, &data)
25932677
if err != nil {
25942678
t.Error(err.Error())
25952679
}
2596-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
2597-
2680+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
2681+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
2682+
assert.Equal(t, "email", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
25982683
}
25992684

26002685
func TestInputFormFieldWithoutPasswordField(t *testing.T) {
@@ -2654,13 +2739,15 @@ func TestInputFormFieldWithoutPasswordField(t *testing.T) {
26542739
}
26552740
resp.Body.Close()
26562741

2657-
assert.Equal(t, 400, resp.StatusCode)
2742+
assert.Equal(t, 200, resp.StatusCode)
26582743
var data map[string]interface{}
26592744
err = json.Unmarshal(dataInBytes, &data)
26602745
if err != nil {
26612746
t.Error(err.Error())
26622747
}
2663-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
2748+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
2749+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
2750+
assert.Equal(t, "password", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
26642751
}
26652752

26662753
func TestInputFormFieldHasADifferentNumberOfCustomFiledsThanInConfigFormFields(t *testing.T) {
@@ -2741,13 +2828,15 @@ func TestInputFormFieldHasADifferentNumberOfCustomFiledsThanInConfigFormFields(t
27412828
}
27422829
resp.Body.Close()
27432830

2744-
assert.Equal(t, 400, resp.StatusCode)
2831+
assert.Equal(t, 200, resp.StatusCode)
27452832
var data map[string]interface{}
27462833
err = json.Unmarshal(dataInBytes, &data)
27472834
if err != nil {
27482835
t.Error(err.Error())
27492836
}
2750-
assert.Equal(t, "Are you sending too many / too few formFields?", data["message"].(string))
2837+
assert.Equal(t, "FIELD_ERROR", data["status"].(string))
2838+
assert.Equal(t, 1, len(data["formFields"].([]interface{})))
2839+
assert.Equal(t, "testField2", (data["formFields"].([]interface{}))[0].(map[string]interface{})["id"].(string))
27512840

27522841
}
27532842

@@ -3177,3 +3266,89 @@ func TestSignUpAPIWorksWhenInputIsFine(t *testing.T) {
31773266
assert.Equal(t, "OK", result["status"])
31783267
assert.Equal(t, "[email protected]", result["user"].(map[string]interface{})["email"])
31793268
}
3269+
3270+
func TestInputFormFieldHasMoreNumberOfCustomFiledsThanInConfigFormFields(t *testing.T) {
3271+
configValue := supertokens.TypeInput{
3272+
Supertokens: &supertokens.ConnectionInfo{
3273+
ConnectionURI: "http://localhost:8080",
3274+
},
3275+
AppInfo: supertokens.AppInfo{
3276+
APIDomain: "api.supertokens.io",
3277+
AppName: "SuperTokens",
3278+
WebsiteDomain: "supertokens.io",
3279+
},
3280+
RecipeList: []supertokens.Recipe{
3281+
Init(&epmodels.TypeInput{
3282+
SignUpFeature: &epmodels.TypeInputSignUp{
3283+
FormFields: []epmodels.TypeInputFormField{
3284+
{
3285+
ID: "testField2",
3286+
},
3287+
},
3288+
},
3289+
}),
3290+
session.Init(&sessmodels.TypeInput{
3291+
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
3292+
return sessmodels.CookieTransferMethod
3293+
},
3294+
}),
3295+
},
3296+
}
3297+
3298+
BeforeEach()
3299+
unittesting.StartUpST("localhost", "8080")
3300+
defer AfterEach()
3301+
err := supertokens.Init(configValue)
3302+
if err != nil {
3303+
t.Error(err.Error())
3304+
}
3305+
mux := http.NewServeMux()
3306+
testServer := httptest.NewServer(supertokens.Middleware(mux))
3307+
defer testServer.Close()
3308+
3309+
formFields := map[string][]map[string]string{
3310+
"formFields": {
3311+
{
3312+
"id": "password",
3313+
"value": "validpass123",
3314+
},
3315+
{
3316+
"id": "email",
3317+
"value": "[email protected]",
3318+
},
3319+
{
3320+
"id": "testField",
3321+
"value": "",
3322+
},
3323+
{
3324+
"id": "testField2",
3325+
"value": "",
3326+
},
3327+
},
3328+
}
3329+
3330+
postBody, err := json.Marshal(formFields)
3331+
if err != nil {
3332+
t.Error(err.Error())
3333+
}
3334+
3335+
resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", bytes.NewBuffer(postBody))
3336+
3337+
if err != nil {
3338+
t.Error(err.Error())
3339+
}
3340+
3341+
dataInBytes, err := io.ReadAll(resp.Body)
3342+
if err != nil {
3343+
t.Error(err.Error())
3344+
}
3345+
resp.Body.Close()
3346+
3347+
assert.Equal(t, 400, resp.StatusCode)
3348+
var data map[string]interface{}
3349+
err = json.Unmarshal(dataInBytes, &data)
3350+
if err != nil {
3351+
t.Error(err.Error())
3352+
}
3353+
assert.Equal(t, "Are you sending too many formFields?", data["message"].(string))
3354+
}

0 commit comments

Comments
 (0)