added user_password tests
This commit is contained in:
@@ -50,7 +50,7 @@ var (
|
|||||||
Cc *ContactController
|
Cc *ContactController
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSuite(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
_ = deleteTestDB("test.db")
|
_ = deleteTestDB("test.db")
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
@@ -136,7 +136,9 @@ func TestSuite(t *testing.T) {
|
|||||||
t.Run("userController", func(t *testing.T) {
|
t.Run("userController", func(t *testing.T) {
|
||||||
testUserController(t)
|
testUserController(t)
|
||||||
})
|
})
|
||||||
|
t.Run("Password_Controller", func(t *testing.T) {
|
||||||
|
|
||||||
|
})
|
||||||
t.Run("SQL_Injection", func(t *testing.T) {
|
t.Run("SQL_Injection", func(t *testing.T) {
|
||||||
testSQLInjectionAttempt(t)
|
testSQLInjectionAttempt(t)
|
||||||
})
|
})
|
||||||
@@ -152,7 +154,6 @@ func TestSuite(t *testing.T) {
|
|||||||
t.Run("XSSAttempt", func(t *testing.T) {
|
t.Run("XSSAttempt", func(t *testing.T) {
|
||||||
testXSSAttempt(t)
|
testXSSAttempt(t)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := utils.SMTPStop(); err != nil {
|
if err := utils.SMTPStop(); err != nil {
|
||||||
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
|
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
|
||||||
}
|
}
|
||||||
@@ -278,10 +279,29 @@ func getBaseUser() models.User {
|
|||||||
ProfilePicture: "",
|
ProfilePicture: "",
|
||||||
Password: "passw@#$#%$!-ord123",
|
Password: "passw@#$#%$!-ord123",
|
||||||
Company: "",
|
Company: "",
|
||||||
RoleID: 8,
|
RoleID: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseSupporter() models.User {
|
||||||
|
return models.User{
|
||||||
|
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
FirstName: "John",
|
||||||
|
LastName: "Rich",
|
||||||
|
Email: "john.supporter@example.com",
|
||||||
|
Address: "Pablo Escobar Str. 4",
|
||||||
|
ZipCode: "25474",
|
||||||
|
City: "Hasloh",
|
||||||
|
Phone: "01738484993",
|
||||||
|
BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||||
|
Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
|
||||||
|
Licence: nil,
|
||||||
|
ProfilePicture: "",
|
||||||
|
Password: "passw@#$#%$!-ord123",
|
||||||
|
Company: "",
|
||||||
|
RoleID: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
func deleteTestDB(dbPath string) error {
|
func deleteTestDB(dbPath string) error {
|
||||||
err := os.Remove(dbPath)
|
err := os.Remove(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ func (uc *UserController) CreatePasswordHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.AccessControl) {
|
if !utils.HasPrivilige(requestUser, constants.Priviliges.AccessControl) {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle all users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle other users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// Expected data from the user
|
// Expected data from the user
|
||||||
var input struct {
|
var input struct {
|
||||||
User struct {
|
User struct {
|
||||||
|
|||||||
211
go-backend/internal/controllers/user_Password_test.go
Normal file
211
go-backend/internal/controllers/user_Password_test.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/config"
|
||||||
|
"GoMembership/internal/constants"
|
||||||
|
"GoMembership/internal/database"
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/utils"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestContext struct {
|
||||||
|
router *gin.Engine
|
||||||
|
response *httptest.ResponseRecorder
|
||||||
|
user *models.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestContext() (*TestContext, error) {
|
||||||
|
|
||||||
|
user, err := Uc.Service.GetUserByEmail("john.doe@example.com")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &TestContext{
|
||||||
|
router: gin.Default(),
|
||||||
|
response: httptest.NewRecorder(),
|
||||||
|
user: user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func testCreatePasswordHandler(t *testing.T, loginCookie http.Cookie, adminCookie http.Cookie) {
|
||||||
|
invalidCookie := http.Cookie{
|
||||||
|
Name: "jwt",
|
||||||
|
Value: "invalid.token.here",
|
||||||
|
}
|
||||||
|
tc, err := setupTestContext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tc.router.POST("/password", Uc.CreatePasswordHandler)
|
||||||
|
requestBody := map[string]interface{}{
|
||||||
|
"user": map[string]interface{}{
|
||||||
|
"id": tc.user.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(requestBody)
|
||||||
|
t.Run("successful password creation request from admin", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(&adminCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusAccepted, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"message":"password_change_requested"}`, tc.response.Body.String())
|
||||||
|
err = checkEmailDelivery(tc.user, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// test token and password change
|
||||||
|
testChangePassword(t, tc)
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("failed password creation request from member", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(&loginCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusForbidden, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"errors":[{"field":"user.user","key":"server.error.unauthorized"}]}`, tc.response.Body.String())
|
||||||
|
err = checkEmailDelivery(tc.user, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("failed password creation request for invalid cookie", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(&invalidCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)
|
||||||
|
err = checkEmailDelivery(tc.user, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testChangePassword(t *testing.T, tc *TestContext) {
|
||||||
|
var verification models.Verification
|
||||||
|
result := database.DB.Where("user_id = ? AND type = ?", tc.user.ID, constants.VerificationTypes.Password).First(&verification)
|
||||||
|
assert.NoError(t, result.Error)
|
||||||
|
logger.Error.Printf("token from db: %#v", verification.VerificationToken)
|
||||||
|
requestBody := map[string]interface{}{
|
||||||
|
"password": "new-pas9247A@!sword",
|
||||||
|
"token": verification.VerificationToken,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(requestBody)
|
||||||
|
tc.router.PUT("/users/:id/password", Uc.ChangePassword)
|
||||||
|
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("valid password change", func(t *testing.T) {
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("PUT", fmt.Sprintf("/users/%v/password", tc.user.ID), bytes.NewBuffer(body))
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"message":"password_changed"}`, tc.response.Body.String())
|
||||||
|
})
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
//User should now be deactivated. Should lack privileges.
|
||||||
|
t.Run("user lacks privileges", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)
|
||||||
|
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
t.Run("invalid user ID", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("PUT", "/users/invalid/password", nil)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
})
|
||||||
|
t.Run("non existant user ID", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("PUT", "/users/999/password", nil)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkEmailDelivery(user *models.User, wantsSuccess bool) error {
|
||||||
|
|
||||||
|
//check for email delivery
|
||||||
|
messages := utils.SMTPGetMessages()
|
||||||
|
for _, message := range messages {
|
||||||
|
mail, err := utils.DecodeMail(message.MsgRequest())
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("Error in validateUser: %#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.Contains(mail.Subject, constants.MailChangePasswordSubject) || strings.Contains(mail.Subject, constants.MailGrantBackendAccessSubject) {
|
||||||
|
|
||||||
|
if err := checkPasswordMail(mail, user); err != nil && wantsSuccess {
|
||||||
|
logger.Error.Printf("Error in checkEmailDelivery mail: %#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Subject not expected: %v", mail.Subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPasswordMail(message *utils.Email, user *models.User) error {
|
||||||
|
var verification models.Verification
|
||||||
|
result := database.DB.Where("user_id = ? AND type = ?", user.ID, constants.VerificationTypes.Password).First(&verification)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
logger.Error.Printf("user id: %v token: %#v", user.ID, verification.VerificationToken)
|
||||||
|
re := regexp.MustCompile(`"([^"]*token[^"]*)"`)
|
||||||
|
|
||||||
|
// Find the matching URL in the email content
|
||||||
|
match := re.FindStringSubmatch(message.Body)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return fmt.Errorf("No change Password link found in email body: %#v", message.Body)
|
||||||
|
}
|
||||||
|
tokenURL, err := url.QueryUnescape(match[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error decoding URL: %v", err)
|
||||||
|
}
|
||||||
|
logger.Info.Printf("TokenURL: %#v", tokenURL)
|
||||||
|
|
||||||
|
if !strings.Contains(message.To, user.Email) {
|
||||||
|
return fmt.Errorf("Password Information didn't reach user! Recipient was: %v instead of %v", message.To, user.Email)
|
||||||
|
}
|
||||||
|
if !strings.Contains(message.From, config.SMTP.User) {
|
||||||
|
return fmt.Errorf("Password Information was sent from unexpected address! Sender was: %v instead of %v", message.From, config.SMTP.User)
|
||||||
|
}
|
||||||
|
//Check if all the relevant data has been passed to the mail.
|
||||||
|
if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
|
||||||
|
return fmt.Errorf("User first and last name(%v) has not been rendered in password mail.", user.FirstName+" "+user.LastName)
|
||||||
|
}
|
||||||
|
if !strings.Contains(message.Body, verification.VerificationToken) {
|
||||||
|
return fmt.Errorf("Token(%v) has not been rendered in password mail.", verification.VerificationToken)
|
||||||
|
}
|
||||||
|
if strings.Trim(tokenURL, " ") != fmt.Sprintf("%v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken) {
|
||||||
|
return fmt.Errorf("Token has not been rendered correctly in password mail: %v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -82,13 +82,12 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user models.User
|
|
||||||
var updateData RegistrationData
|
var updateData RegistrationData
|
||||||
if err := c.ShouldBindJSON(&updateData); err != nil {
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user = updateData.User
|
user := updateData.User
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && user.ID != requestUser.ID {
|
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && user.ID != requestUser.ID {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
@@ -109,6 +108,7 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
user.BankAccountID = existingUser.BankAccountID
|
user.BankAccountID = existingUser.BankAccountID
|
||||||
|
|
||||||
if requestUser.RoleID <= constants.Priviliges.View {
|
if requestUser.RoleID <= constants.Priviliges.View {
|
||||||
|
// deleting existing Users Password to prevent it from being recognized as changed in any case. (Incoming Password is empty if not changed)
|
||||||
existingUser.Password = ""
|
existingUser.Password = ""
|
||||||
if err := utils.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
|
if err := utils.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
|
||||||
if err.Error() == "Not authorized" {
|
if err.Error() == "Not authorized" {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ func testUserController(t *testing.T) {
|
|||||||
assert.NotEmpty(t, adminCookie)
|
assert.NotEmpty(t, adminCookie)
|
||||||
testUpdateUser(t, loginCookie, adminCookie)
|
testUpdateUser(t, loginCookie, adminCookie)
|
||||||
testLogoutHandler(t, logoutCookie)
|
testLogoutHandler(t, logoutCookie)
|
||||||
|
testCreatePasswordHandler(t, loginCookie, adminCookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
||||||
|
|||||||
Reference in New Issue
Block a user