newsbot-api/internal/repositoryServices/userService.go

199 lines
5.0 KiB
Go

package repositoryservices
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"git.jamestombleson.com/jtom38/newsbot-api/domain"
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
"github.com/google/uuid"
"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) (entity.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) (entity.UserEntity, error)
NewSessionToken(ctx context.Context, name string) (string, 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) (entity.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) (entity.UserEntity, error) {
err := us.CheckPasswordForRequirements(password)
if err != nil {
return entity.UserEntity{}, err
}
token, err := uuid.NewV7()
if err != nil {
return entity.UserEntity{}, err
}
us.repo.Create(ctx, name, password, token.String(), domain.ScopeArticleRead)
return entity.UserEntity{}, nil
}
func (us UserService) NewSessionToken(ctx context.Context, name string) (string, error) {
token, err := uuid.NewV7()
if err != nil {
return "", err
}
rows, err := us.repo.UpdateSessionToken(ctx, name, token.String())
if err != nil {
return "", err
}
if rows != 1 {
return "", fmt.Errorf("UserService.NewSessionToken %w", err)
}
return token.String(), 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)
}