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

Added strict validation for username and password in backend. #4670

Merged
Show file tree
Hide file tree
Changes from 5 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
21 changes: 20 additions & 1 deletion chaoscenter/authentication/api/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,12 @@ const docTemplate = `{
"$ref": "#/definitions/response.ErrInvalidRequest"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.ErrStrictUsernamePolicyViolation"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
Expand Down Expand Up @@ -1214,7 +1220,20 @@ const docTemplate = `{
},
"message": {
"type": "string",
"example": "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
"example": "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"
}
}
},
"response.ErrStrictUsernamePolicyViolation": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 401
},
"message": {
"type": "string",
"example": "The username should be atleast 3 characters long and atmost 16 characters long."
}
}
},
Expand Down
21 changes: 20 additions & 1 deletion chaoscenter/authentication/api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,12 @@
"$ref": "#/definitions/response.ErrInvalidRequest"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.ErrStrictUsernamePolicyViolation"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
Expand Down Expand Up @@ -1204,7 +1210,20 @@
},
"message": {
"type": "string",
"example": "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character"
"example": "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"
}
}
},
"response.ErrStrictUsernamePolicyViolation": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 401
},
"message": {
"type": "string",
"example": "The username should be atleast 3 characters long and atmost 16 characters long."
}
}
},
Expand Down
19 changes: 17 additions & 2 deletions chaoscenter/authentication/api/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,19 @@ definitions:
example: 401
type: integer
message:
example: Please ensure the password is 8 characters long and has 1 digit,
1 lowercase alphabet, 1 uppercase alphabet and 1 special character
example: 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
type: string
type: object
response.ErrStrictUsernamePolicyViolation:
properties:
code:
example: 401
type: integer
message:
example: The username should be atleast 3 characters long and atmost 16 characters
long.
type: string
type: object
response.ErrUnauthorized:
Expand Down Expand Up @@ -762,6 +773,10 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/response.ErrInvalidRequest'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.ErrStrictUsernamePolicyViolation'
"500":
description: Internal Server Error
schema:
Expand Down
7 changes: 6 additions & 1 deletion chaoscenter/authentication/api/handlers/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ type ErrUserDeactivated struct {

type ErrStrictPasswordPolicyViolation struct {
Code int `json:"code" example:"401"`
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"`
Message string `json:"message" example:"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"`
aryan-bhokare marked this conversation as resolved.
Show resolved Hide resolved
}

type ErrStrictUsernamePolicyViolation struct {
Code int `json:"code" example:"401"`
Message string `json:"message" example:"The username should be atleast 3 characters long and atmost 16 characters long."`
}

type ErrEmptyProjectName struct {
Expand Down
38 changes: 34 additions & 4 deletions chaoscenter/authentication/api/handlers/rest/user_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const BearerSchema = "Bearer "
// @Failure 400 {object} response.ErrInvalidRequest
// @Failure 401 {object} response.ErrUnauthorized
// @Failure 400 {object} response.ErrInvalidEmail
// @Failure 401 {object} response.ErrStrictPasswordPolicyViolation
// @Failure 401 {object} response.ErrStrictUsernamePolicyViolation
// @Failure 401 {object} response.ErrUserExists
// @Failure 500 {object} response.ErrServerError
// @Success 200 {object} response.UserResponse{}
Expand Down Expand Up @@ -57,6 +59,19 @@ func CreateUser(service services.ApplicationService) gin.HandlerFunc {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
return
}
//username validation
err = utils.ValidateStrictUsername(userRequest.Username)
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictUsernamePolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictUsernamePolicyViolation))
return
}

// password validation
err = utils.ValidateStrictPassword(userRequest.Password)
aryan-bhokare marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))
return
}

// Assigning UID to user
uID := uuid.Must(uuid.NewRandom()).String()
Expand Down Expand Up @@ -105,6 +120,8 @@ func CreateUser(service services.ApplicationService) gin.HandlerFunc {
// @Accept json
// @Produce json
// @Failure 400 {object} response.ErrInvalidRequest
// @Failure 401 {object} response.ErrStrictPasswordPolicyViolation
// @Failure 401 {object} response.ErrStrictUsernamePolicyViolation
// @Failure 500 {object} response.ErrServerError
// @Success 200 {object} response.MessageResponse{}
// @Router /update/details [post]
Expand All @@ -123,13 +140,26 @@ func UpdateUser(service services.ApplicationService) gin.HandlerFunc {

// Checking if password is updated
if userRequest.Password != "" {
err := utils.ValidateStrictPassword(userRequest.Password)
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(userRequest.Password), utils.PasswordEncryptionCost)
if err != nil {
return
}
userRequest.Password = string(hashedPassword)
}

if userRequest.Name != "" {
err = utils.ValidateStrictUsername(userRequest.Name)
aryan-bhokare marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictUsernamePolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictUsernamePolicyViolation))
return
}
}

err = service.UpdateUser(&userRequest)
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
Expand Down Expand Up @@ -405,17 +435,17 @@ func UpdatePassword(service services.ApplicationService) gin.HandlerFunc {
}
username := c.MustGet("username").(string)
userPasswordRequest.Username = username
if utils.StrictPasswordPolicy {
if userPasswordRequest.NewPassword != "" {
err := utils.ValidateStrictPassword(userPasswordRequest.NewPassword)
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))
return
}
}
if userPasswordRequest.NewPassword == "" {
} else {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
return
}

err = service.UpdatePassword(&userPasswordRequest, true)
if err != nil {
log.Info(err)
Expand Down Expand Up @@ -460,7 +490,7 @@ func ResetPassword(service services.ApplicationService) gin.HandlerFunc {
var adminUser entities.User
adminUser.Username = c.MustGet("username").(string)
adminUser.ID = uid
if utils.StrictPasswordPolicy {
if userPasswordRequest.NewPassword != "" {
err := utils.ValidateStrictPassword(userPasswordRequest.NewPassword)
if err != nil {
c.JSON(utils.ErrorStatusCodes[utils.ErrStrictPasswordPolicyViolation], presenter.CreateErrorResponse(utils.ErrStrictPasswordPolicyViolation))
Expand Down
22 changes: 13 additions & 9 deletions chaoscenter/authentication/api/handlers/rest/user_handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestCreateUser(t *testing.T) {
name: "successfully",
inputBody: &entities.User{
Username: "newUser",
Password: "validPassword123",
Password: "ValidPassword@1",
Email: "newuser@example.com",
Name: "John Doe",
Role: entities.RoleUser,
Expand All @@ -66,7 +66,7 @@ func TestCreateUser(t *testing.T) {
Email: "newuser@example.com",
Name: "John Doe",
Role: entities.RoleUser,
}, nil)
}, nil).Once()
},
expectedCode: 200,
},
Expand All @@ -78,8 +78,12 @@ func TestCreateUser(t *testing.T) {
c, _ := gin.CreateTestContext(w)
c.Set("role", tc.mockRole)
if tc.inputBody != nil {
b, _ := json.Marshal(tc.inputBody)
b, err := json.Marshal(tc.inputBody)
if err != nil {
t.Fatalf("could not marshal input body: %v", err)
}
c.Request = httptest.NewRequest(http.MethodPost, "/users", bytes.NewBuffer(b))
c.Request.Header.Set("Content-Type", "application/json")
}

tc.given()
Expand All @@ -104,7 +108,7 @@ func TestUpdateUser(t *testing.T) {
{
name: "Successful update with password",
uid: "testUID",
inputBody: &entities.UserDetails{Email: "test@email.com", Name: "Test", Password: "TestPassword"},
inputBody: &entities.UserDetails{Email: "test@email.com", Name: "Test", Password: "TestPassword@123"},
given: func() {
service.On("UpdateUser", mock.AnythingOfType("*entities.UserDetails")).Return(nil)
},
Expand Down Expand Up @@ -418,7 +422,7 @@ func TestUpdatePassword(t *testing.T) {
}{
{
name: "Successfully update password",
givenBody: `{"oldPassword":"oldPass", "newPassword":"newPass"}`,
givenBody: `{"oldPassword":"oldPass@123", "newPassword":"newPass@123"}`,
givenUsername: "testUser",
givenStrictPassword: false,
givenServiceResponse: nil,
Expand All @@ -439,8 +443,8 @@ func TestUpdatePassword(t *testing.T) {

userPassword := entities.UserPassword{
Username: tt.givenUsername,
OldPassword: "oldPass",
NewPassword: "newPass",
OldPassword: "oldPass@123",
NewPassword: "newPass@123",
}
service.On("UpdatePassword", &userPassword, true).Return(tt.givenServiceResponse)

Expand Down Expand Up @@ -470,7 +474,7 @@ func TestResetPassword(t *testing.T) {
inputBody: &entities.UserPassword{
Username: "testUser",
OldPassword: "",
NewPassword: "validPassword123",
NewPassword: "ValidPass@123",
},
mockRole: "admin",
mockUID: "testUID",
Expand All @@ -486,7 +490,7 @@ func TestResetPassword(t *testing.T) {
inputBody: &entities.UserPassword{
Username: "testUser",
OldPassword: "",
aryan-bhokare marked this conversation as resolved.
Show resolved Hide resolved
NewPassword: "validPassword123",
NewPassword: "validPass@123",
},
mockRole: "user",
mockUID: "testUID",
Expand Down
5 changes: 4 additions & 1 deletion chaoscenter/authentication/pkg/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
ErrServerError AppError = errors.New("server_error")
ErrInvalidRequest AppError = errors.New("invalid_request")
ErrStrictPasswordPolicyViolation AppError = errors.New("password_policy_violation")
ErrStrictUsernamePolicyViolation AppError = errors.New("username_policy_violation")
ErrUnauthorized AppError = errors.New("unauthorized")
ErrUserExists AppError = errors.New("user_exists")
ErrUserNotFound AppError = errors.New("user does not exist")
Expand All @@ -31,6 +32,7 @@ var ErrorStatusCodes = map[AppError]int{
ErrUnauthorized: 401,
ErrUserExists: 401,
ErrStrictPasswordPolicyViolation: 401,
ErrStrictUsernamePolicyViolation: 401,
ErrUserNotFound: 400,
ErrProjectNotFound: 400,
ErrUpdatingAdmin: 400,
Expand All @@ -48,7 +50,8 @@ var ErrorDescriptions = map[AppError]string{
ErrInvalidRequest: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
ErrUnauthorized: "The user does not have requested authorization to access this resource",
ErrUserExists: "This username is already assigned to another user",
ErrStrictPasswordPolicyViolation: "Please ensure the password is 8 characters long and has 1 digit, 1 lowercase alphabet, 1 uppercase alphabet and 1 special character",
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",
ErrStrictUsernamePolicyViolation: "The username should be atleast 3 characters long and atmost 16 characters long.",
ErrEmptyProjectName: "Project name can't be empty",
ErrInvalidRole: "Role is invalid",
ErrProjectNotFound: "This project does not exist",
Expand Down
24 changes: 21 additions & 3 deletions chaoscenter/authentication/pkg/utils/sanitizers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ func SanitizeString(input string) string {

/*
ValidateStrictPassword represents and checks for the following patterns:
- Input is at least 8 characters long
- Input contains at least one special character
- Input is at least 8 characters long and at most 16 characters long
- Input contains at least one special character of these @$!%*?_&
- Input contains at least one digit
- Input contains at least one uppercase alphabet
- Input contains at least one lowercase alphabet
Expand All @@ -23,10 +23,15 @@ func ValidateStrictPassword(input string) error {
if len(input) < 8 {
return fmt.Errorf("password is less than 8 characters")
}

if len(input) > 16 {
SarthakJain26 marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("password is more than 16 characters")
aryan-bhokare marked this conversation as resolved.
Show resolved Hide resolved
}

digits := `[0-9]{1}`
lowerAlphabets := `[a-z]{1}`
capitalAlphabets := `[A-Z]{1}`
specialCharacters := `[!@#~$%^&*()+|_]{1}`
specialCharacters := `[@$!%*?_&]{1}`
if b, err := regexp.MatchString(digits, input); !b || err != nil {
return fmt.Errorf("password does not contain digits")
}
Expand All @@ -41,3 +46,16 @@ func ValidateStrictPassword(input string) error {
}
return nil
}

// Username must start with a letter - ^[a-zA-Z]
// Allow letters, digits, underscores, and hyphens - [a-zA-Z0-9_-]
// Ensure the length of the username is between 3 and 16 characters (1 character is already matched above) - {2,15}$

func ValidateStrictUsername(username string) error {
// Ensure username doesn't contain special characters (only letters, numbers, and underscores are allowed)
if matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_-]{2,15}$`, username); !matched {
return fmt.Errorf("username can only contain letters, numbers, and underscores")
}

return nil
}
Loading