Skip to content

Commit 9d2c93a

Browse files
aryan-bhokareJonsy13Saranya-jena
authored
Added strict validation for username and password in backend. (#4670)
* Added strict validation for username and password in backend. Signed-off-by: aryan <[email protected]> * fixed silly mistake Signed-off-by: aryan <[email protected]> * some requested changes. Signed-off-by: aryan <[email protected]> * modified tests Signed-off-by: aryan <[email protected]> * small change Signed-off-by: aryan <[email protected]> * Update message string in chaoscenter/authentication/api/handlers/doc.go Co-authored-by: Vedant Shrotria <[email protected]> Signed-off-by: Aryan Bhokare <[email protected]> * Update error message chaoscenter/authentication/pkg/utils/sanitizers.go Co-authored-by: Vedant Shrotria <[email protected]> Signed-off-by: Aryan Bhokare <[email protected]> * Modified swagger with requested changes. Signed-off-by: aryan <[email protected]> * added negative tests for requested functions and fixed some conflicts. Signed-off-by: aryan <[email protected]> --------- Signed-off-by: aryan <[email protected]> Signed-off-by: Aryan Bhokare <[email protected]> Co-authored-by: Vedant Shrotria <[email protected]> Co-authored-by: Saranya Jena <[email protected]>
1 parent 544d324 commit 9d2c93a

File tree

8 files changed

+148
-23
lines changed

8 files changed

+148
-23
lines changed

chaoscenter/authentication/api/docs/docs.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,12 @@ const docTemplate = `{
940940
"$ref": "#/definitions/response.ErrInvalidRequest"
941941
}
942942
},
943+
"401": {
944+
"description": "Unauthorized",
945+
"schema": {
946+
"$ref": "#/definitions/response.ErrStrictUsernamePolicyViolation"
947+
}
948+
},
943949
"500": {
944950
"description": "Internal Server Error",
945951
"schema": {
@@ -1214,7 +1220,20 @@ const docTemplate = `{
12141220
},
12151221
"message": {
12161222
"type": "string",
1217-
"example": "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
1223+
"example": "Please ensure the password is atleast 8 characters and atmost 16 characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
1224+
}
1225+
}
1226+
},
1227+
"response.ErrStrictUsernamePolicyViolation": {
1228+
"type": "object",
1229+
"properties": {
1230+
"code": {
1231+
"type": "integer",
1232+
"example": 401
1233+
},
1234+
"message": {
1235+
"type": "string",
1236+
"example": "The username should be atleast 3 characters long and atmost 16 characters long."
12181237
}
12191238
}
12201239
},

chaoscenter/authentication/api/docs/swagger.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,12 @@
930930
"$ref": "#/definitions/response.ErrInvalidRequest"
931931
}
932932
},
933+
"401": {
934+
"description": "Unauthorized",
935+
"schema": {
936+
"$ref": "#/definitions/response.ErrStrictUsernamePolicyViolation"
937+
}
938+
},
933939
"500": {
934940
"description": "Internal Server Error",
935941
"schema": {
@@ -1204,7 +1210,20 @@
12041210
},
12051211
"message": {
12061212
"type": "string",
1207-
"example": "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
1213+
"example": "Please ensure the password is atleast 8 characters and atmost 16 characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
1214+
}
1215+
}
1216+
},
1217+
"response.ErrStrictUsernamePolicyViolation": {
1218+
"type": "object",
1219+
"properties": {
1220+
"code": {
1221+
"type": "integer",
1222+
"example": 401
1223+
},
1224+
"message": {
1225+
"type": "string",
1226+
"example": "The username should be atleast 3 characters long and atmost 16 characters long."
12081227
}
12091228
}
12101229
},

chaoscenter/authentication/api/docs/swagger.yaml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,19 @@ definitions:
8282
example: 401
8383
type: integer
8484
message:
85-
example: Please ensure the password is 8 characters long and has 1 digit,
86-
1 lowercase alphabet, 1 uppercase alphabet and 1 special character
85+
example: Please ensure the password is atleast 8 characters and atmost 16
86+
characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase
87+
alphabet and 1 special character
88+
type: string
89+
type: object
90+
response.ErrStrictUsernamePolicyViolation:
91+
properties:
92+
code:
93+
example: 401
94+
type: integer
95+
message:
96+
example: The username should be atleast 3 characters long and atmost 16 characters
97+
long.
8798
type: string
8899
type: object
89100
response.ErrUnauthorized:
@@ -762,6 +773,10 @@ paths:
762773
description: Bad Request
763774
schema:
764775
$ref: '#/definitions/response.ErrInvalidRequest'
776+
"401":
777+
description: Unauthorized
778+
schema:
779+
$ref: '#/definitions/response.ErrStrictUsernamePolicyViolation'
765780
"500":
766781
description: Internal Server Error
767782
schema:

chaoscenter/authentication/api/handlers/doc.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ type ErrUserDeactivated struct {
9797

9898
type ErrStrictPasswordPolicyViolation struct {
9999
Code int `json:"code" example:"401"`
100-
Message string `json:"message" example:"Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"`
100+
Message string `json:"message" example:"Please ensure the password is atleast 8 characters and atmost 16 characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"`
101+
}
102+
103+
type ErrStrictUsernamePolicyViolation struct {
104+
Code int `json:"code" example:"401"`
105+
Message string `json:"message" example:"The username should be atleast 3 characters long and atmost 16 characters long."`
101106
}
102107

103108
type ErrEmptyProjectName struct {

chaoscenter/authentication/api/handlers/rest/user_handlers.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const BearerSchema = "Bearer "
2828
// @Failure 400 {object} response.ErrInvalidRequest
2929
// @Failure 401 {object} response.ErrUnauthorized
3030
// @Failure 400 {object} response.ErrInvalidEmail
31+
// @Failure 401 {object} response.ErrStrictPasswordPolicyViolation
32+
// @Failure 401 {object} response.ErrStrictUsernamePolicyViolation
3133
// @Failure 401 {object} response.ErrUserExists
3234
// @Failure 500 {object} response.ErrServerError
3335
// @Success 200 {object} response.UserResponse{}
@@ -59,6 +61,12 @@ func CreateUser(service services.ApplicationService) gin.HandlerFunc {
5961
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
6062
return
6163
}
64+
//username validation
65+
err = utils.ValidateStrictUsername(userRequest.Username)
66+
if err != nil {
67+
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictUsernamePolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictUsernamePolicyViolation))
68+
return
69+
}
6270

6371
// Assigning UID to user
6472
uID := uuid.Must(uuid.NewRandom()).String()
@@ -108,6 +116,8 @@ func CreateUser(service services.ApplicationService) gin.HandlerFunc {
108116
// @Accept json
109117
// @Produce json
110118
// @Failure 400 {object} response.ErrInvalidRequest
119+
// @Failure 401 {object} response.ErrStrictPasswordPolicyViolation
120+
// @Failure 401 {object} response.ErrStrictUsernamePolicyViolation
111121
// @Failure 500 {object} response.ErrServerError
112122
// @Success 200 {object} response.MessageResponse{}
113123
// @Router /update/details [post]
@@ -414,17 +424,17 @@ func UpdatePassword(service services.ApplicationService) gin.HandlerFunc {
414424
}
415425
username := c.MustGet("username").(string)
416426
userPasswordRequest.Username = username
417-
if utils.StrictPasswordPolicy {
427+
if userPasswordRequest.NewPassword != "" {
418428
err := utils.ValidateStrictPassword(userPasswordRequest.NewPassword)
419429
if err != nil {
420430
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))
421431
return
422432
}
423-
}
424-
if userPasswordRequest.NewPassword == "" {
433+
} else {
425434
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
426435
return
427436
}
437+
428438
err = service.UpdatePassword(&userPasswordRequest, true)
429439
if err != nil {
430440
log.Info(err)
@@ -478,7 +488,7 @@ func ResetPassword(service services.ApplicationService) gin.HandlerFunc {
478488
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrPasswordNotUpdated))
479489
}
480490

481-
if utils.StrictPasswordPolicy {
491+
if userPasswordRequest.NewPassword != "" {
482492
err := utils.ValidateStrictPassword(userPasswordRequest.NewPassword)
483493
if err != nil {
484494
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))

chaoscenter/authentication/api/handlers/rest/user_handlers_test.go

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func TestCreateUser(t *testing.T) {
5555
name: "successfully",
5656
inputBody: &entities.User{
5757
Username: "newUser",
58-
Password: "validPassword123",
58+
Password: "ValidPassword@1",
5959
6060
Name: "John Doe",
6161
Role: entities.RoleUser,
@@ -68,7 +68,7 @@ func TestCreateUser(t *testing.T) {
6868
6969
Name: "John Doe",
7070
Role: entities.RoleUser,
71-
}, nil)
71+
}, nil).Once()
7272
},
7373
expectedCode: 200,
7474
},
@@ -80,8 +80,12 @@ func TestCreateUser(t *testing.T) {
8080
c, _ := gin.CreateTestContext(w)
8181
c.Set("role", tc.mockRole)
8282
if tc.inputBody != nil {
83-
b, _ := json.Marshal(tc.inputBody)
83+
b, err := json.Marshal(tc.inputBody)
84+
if err != nil {
85+
t.Fatalf("could not marshal input body: %v", err)
86+
}
8487
c.Request = httptest.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(b))
88+
c.Request.Header.Set("Content-Type", "application/json")
8589
}
8690

8791
tc.given()
@@ -477,13 +481,22 @@ func TestUpdatePassword(t *testing.T) {
477481
}{
478482
{
479483
name: "Successfully update password",
480-
givenBody: `{"oldPassword":"oldPass", "newPassword":"newPass"}`,
484+
givenBody: `{"oldPassword":"oldPass@123", "newPassword":"newPass@123"}`,
481485
givenUsername: "testUser",
482486
givenStrictPassword: false,
483487
givenServiceResponse: nil,
484488
expectedCode: http.StatusOK,
485489
expectedOutput: `{"message":"password has been updated successfully"}`,
486490
},
491+
{
492+
name: "Invalid new password",
493+
givenBody: `{"oldPassword":"oldPass@123", "newPassword":"short"}`,
494+
givenUsername: "testUser",
495+
givenStrictPassword: false,
496+
givenServiceResponse: errors.New("invalid password"),
497+
expectedCode: utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation],
498+
expectedOutput: `{"error":"password_policy_violation","errorDescription":"Please ensure the password is atleast 8 characters long and atmost 16 characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"}`,
499+
},
487500
}
488501

489502
for _, tt := range tests {
@@ -498,8 +511,8 @@ func TestUpdatePassword(t *testing.T) {
498511

499512
userPassword := entities.UserPassword{
500513
Username: tt.givenUsername,
501-
OldPassword: "oldPass",
502-
NewPassword: "newPass",
514+
OldPassword: "oldPass@123",
515+
NewPassword: "newPass@123",
503516
}
504517
user := &entities.User{
505518
ID: "testUID",
@@ -532,11 +545,11 @@ func TestResetPassword(t *testing.T) {
532545
expectedCode int
533546
}{
534547
{
535-
name: "Non-admin role",
548+
name: "Admin role",
536549
inputBody: &entities.UserPassword{
537550
Username: "testUser",
538551
OldPassword: "",
539-
NewPassword: "validPassword123",
552+
NewPassword: "ValidPass@123",
540553
},
541554
mockRole: "admin",
542555
mockUID: "testUID",
@@ -559,7 +572,7 @@ func TestResetPassword(t *testing.T) {
559572
inputBody: &entities.UserPassword{
560573
Username: "testUser",
561574
OldPassword: "",
562-
NewPassword: "validPassword123",
575+
NewPassword: "validPass@123",
563576
},
564577
mockRole: "user",
565578
mockUID: "testUID",
@@ -581,6 +594,29 @@ func TestResetPassword(t *testing.T) {
581594
mockUsername: "adminUser",
582595
expectedCode: utils.ErrorStatusCodes[utils.ErrInvalidRequest],
583596
},
597+
{
598+
name: "Admin role wrong password",
599+
inputBody: &entities.UserPassword{
600+
Username: "testUser",
601+
OldPassword: "",
602+
NewPassword: "short",
603+
},
604+
mockRole: "admin",
605+
mockUID: "testUID",
606+
mockUsername: "adminUser",
607+
given: func() {
608+
user := &entities.User{
609+
ID: "testUID",
610+
Username: "testUser",
611+
612+
IsInitialLogin: false,
613+
}
614+
service.On("GetUser", "testUID").Return(user, nil)
615+
service.On("IsAdministrator", mock.AnythingOfType("*entities.User")).Return(nil)
616+
service.On("UpdatePassword", mock.AnythingOfType("*entities.UserPassword"), false).Return(nil)
617+
},
618+
expectedCode: utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation],
619+
},
584620
}
585621

586622
for _, tt := range tests {

chaoscenter/authentication/pkg/utils/errors.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var (
1010
ErrServerError AppError = errors.New("server_error")
1111
ErrInvalidRequest AppError = errors.New("invalid_request")
1212
ErrStrictPasswordPolicyViolation AppError = errors.New("password_policy_violation")
13+
ErrStrictUsernamePolicyViolation AppError = errors.New("username_policy_violation")
1314
ErrUnauthorized AppError = errors.New("unauthorized")
1415
ErrUserExists AppError = errors.New("user_exists")
1516
ErrUserNotFound AppError = errors.New("user does not exist")
@@ -32,6 +33,7 @@ var ErrorStatusCodes = map[AppError]int{
3233
ErrUnauthorized: 401,
3334
ErrUserExists: 401,
3435
ErrStrictPasswordPolicyViolation: 401,
36+
ErrStrictUsernamePolicyViolation: 401,
3537
ErrUserNotFound: 400,
3638
ErrProjectNotFound: 400,
3739
ErrUpdatingAdmin: 400,
@@ -50,7 +52,8 @@ var ErrorDescriptions = map[AppError]string{
5052
ErrInvalidRequest: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
5153
ErrUnauthorized: "The user does not have requested authorization to access this resource",
5254
ErrUserExists: "This username is already assigned to another user",
53-
ErrStrictPasswordPolicyViolation: "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character",
55+
ErrStrictPasswordPolicyViolation: "Please ensure the password is atleast 8 characters long and atmost 16 characters long and has atleast 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character",
56+
ErrStrictUsernamePolicyViolation: "The username should be atleast 3 characters long and atmost 16 characters long.",
5457
ErrEmptyProjectName: "Project name can't be empty",
5558
ErrInvalidRole: "Role is invalid",
5659
ErrProjectNotFound: "This project does not exist",

chaoscenter/authentication/pkg/utils/sanitizers.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,25 @@ func SanitizeString(input string) string {
1313

1414
/*
1515
ValidateStrictPassword represents and checks for the following patterns:
16-
- Input is at least 8 characters long
17-
- Input contains at least one special character
16+
- Input is at least 8 characters long and at most 16 characters long
17+
- Input contains at least one special character of these @$!%*?_&
1818
- Input contains at least one digit
1919
- Input contains at least one uppercase alphabet
2020
- Input contains at least one lowercase alphabet
2121
*/
2222
func ValidateStrictPassword(input string) error {
2323
if len(input) < 8 {
24-
return fmt.Errorf("password is less than 8 characters")
24+
return fmt.Errorf("password length is less than 8 characters")
2525
}
26+
27+
if len(input) > 16 {
28+
return fmt.Errorf("password length is more than 16 characters")
29+
}
30+
2631
digits := `[0-9]{1}`
2732
lowerAlphabets := `[a-z]{1}`
2833
capitalAlphabets := `[A-Z]{1}`
29-
specialCharacters := `[!@#~$%^&*()+|_]{1}`
34+
specialCharacters := `[@$!%*?_&]{1}`
3035
if b, err := regexp.MatchString(digits, input); !b || err != nil {
3136
return fmt.Errorf("password does not contain digits")
3237
}
@@ -41,3 +46,16 @@ func ValidateStrictPassword(input string) error {
4146
}
4247
return nil
4348
}
49+
50+
// Username must start with a letter - ^[a-zA-Z]
51+
// Allow letters, digits, underscores, and hyphens - [a-zA-Z0-9_-]
52+
// Ensure the length of the username is between 3 and 16 characters (1 character is already matched above) - {2,15}$
53+
54+
func ValidateStrictUsername(username string) error {
55+
// Ensure username doesn't contain special characters (only letters, numbers, and underscores are allowed)
56+
if matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_-]{2,15}$`, username); !matched {
57+
return fmt.Errorf("username can only contain letters, numbers, and underscores")
58+
}
59+
60+
return nil
61+
}

0 commit comments

Comments
 (0)