package services import ( "database/sql" "errors" "strings" "git.jamestombleson.com/jtom38/go-cook/internal/domain" "git.jamestombleson.com/jtom38/go-cook/internal/repositories" "golang.org/x/crypto/bcrypt" ) const ( ErrPasswordNotLongEnough = "password needs to be 8 character or longer" ErrPasswordMissingSpecialCharacter = "password needs to contain one of the following: !, @, #" ErrInvalidPassword = "invalid password" ) type Users interface { DoesUserExist(username string) error DoesPasswordMatchHash(username, password string) error GetUser(username string) (domain.UserEntity, error) AddScopes(username string, scopes []string) error RemoveScopes(username string, scopes []string) error Create(name, password, scope string) (domain.UserEntity, error) CheckPasswordForRequirements(password string) error } // This will handle operations that are user related, but one layer higher then the repository type UserService struct { repo repositories.IUserTable } func NewUserService(conn *sql.DB) UserService { return UserService{ repo: repositories.NewUserRepository(conn), } } func (us UserService) DoesUserExist(username string) error { _, err := us.repo.GetByName(username) if err != nil { return err } return nil } func (us UserService) DoesPasswordMatchHash(username, password string) error { model, err := us.GetUser(username) if err != nil { return err } err = bcrypt.CompareHashAndPassword([]byte(model.Hash), []byte(password)) if err != nil { return errors.New(ErrInvalidPassword) } return nil } func (us UserService) GetUser(username string) (domain.UserEntity, error) { return us.repo.GetByName(username) } func (us UserService) AddScopes(username string, scopes []string) error { usr, err := us.repo.GetByName(username) if err != nil { return err } if usr.Name != username { return errors.New(repositories.ErrUserNotFound) } currentScopes := strings.Split(usr.Scopes, ",") // check the current scopes for _, item := range scopes { if !strings.Contains(usr.Scopes, item) { currentScopes = append(currentScopes, item) } } return us.repo.UpdateScopes(username, strings.Join(currentScopes, ",")) } func (us UserService) RemoveScopes(username string, scopes []string) error { usr, err := us.repo.GetByName(username) if err != nil { return err } if usr.Name != username { return errors.New(repositories.ErrUserNotFound) } var newScopes []string // check all the scopes that are currently assigned for _, item := range strings.Split(usr.Scopes, ",") { // check the scopes given, if one matches skip it if us.doesScopeExist(scopes, item) { continue } // did not match, add it newScopes = append(newScopes, item) } return us.repo.UpdateScopes(username, strings.Join(newScopes, ",")) } func (us UserService) doesScopeExist(scopes []string, target string) bool { for _, item := range scopes { if item == target { return true } } return false } func (us UserService) Create(name, password, scope string) (domain.UserEntity, error) { err := us.CheckPasswordForRequirements(password) if err != nil { return domain.UserEntity{}, err } us.repo.Create(name, password, domain.ScopeRecipeRead) return domain.UserEntity{}, nil } func (us UserService) CheckPasswordForRequirements(password string) error { err := us.checkPasswordLength(password) if err != nil { return err } err = us.checkPasswordForSpecialCharacters(password) if err != nil { return err } return nil } func (us UserService) checkPasswordLength(password string) error { if len(password) < 8 { return errors.New(ErrPasswordNotLongEnough) } return nil } func (us UserService) checkPasswordForSpecialCharacters(password string) error { var chars []string chars = append(chars, "!") chars = append(chars, "@") chars = append(chars, "#") for _, char := range chars { if strings.Contains(password, char) { return nil } } return errors.New(ErrPasswordMissingSpecialCharacter) }