2024-05-07 22:20:50 -07:00
|
|
|
package repositoryservices
|
2024-05-05 10:02:17 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/domain"
|
|
|
|
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
|
|
|
|
|
|
|
"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 UserServices interface {
|
|
|
|
DoesUserExist(ctx context.Context, username string) error
|
|
|
|
DoesPasswordMatchHash(ctx context.Context, username, password string) error
|
|
|
|
GetUser(ctx context.Context, username string) (domain.UserEntity, error)
|
|
|
|
AddScopes(ctx context.Context, username string, scopes []string) error
|
|
|
|
RemoveScopes(ctx context.Context, username string, scopes []string) error
|
|
|
|
Create(ctx context.Context, 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 repository.Users
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a layer on top of the Users Repository.
|
|
|
|
// Use this over directly talking to the table when ever possible.
|
|
|
|
func NewUserService(conn *sql.DB) UserService {
|
|
|
|
return UserService{
|
|
|
|
repo: repository.NewUserRepository(conn),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (us UserService) DoesUserExist(ctx context.Context, username string) error {
|
|
|
|
_, err := us.repo.GetByName(ctx, username)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (us UserService) DoesPasswordMatchHash(ctx context.Context, username, password string) error {
|
|
|
|
model, err := us.GetUser(ctx, 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(ctx context.Context, username string) (domain.UserEntity, error) {
|
|
|
|
return us.repo.GetByName(ctx, username)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (us UserService) AddScopes(ctx context.Context, username string, scopes []string) error {
|
|
|
|
usr, err := us.repo.GetByName(ctx, username)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if usr.Username != username {
|
|
|
|
return errors.New(repository.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(ctx, username, strings.Join(currentScopes, ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (us UserService) RemoveScopes(ctx context.Context, username string, scopes []string) error {
|
|
|
|
usr, err := us.repo.GetByName(ctx, username)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if usr.Username != username {
|
|
|
|
return errors.New(repository.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(ctx, 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(ctx context.Context, name, password, scope string) (domain.UserEntity, error) {
|
|
|
|
err := us.CheckPasswordForRequirements(password)
|
|
|
|
if err != nil {
|
|
|
|
return domain.UserEntity{}, err
|
|
|
|
}
|
|
|
|
|
2024-05-07 19:14:37 -07:00
|
|
|
us.repo.Create(ctx, name, password, domain.ScopeArticleRead)
|
2024-05-05 10:02:17 -07:00
|
|
|
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)
|
|
|
|
}
|