vibeStonk/server/services/authService.go
2025-06-12 16:57:42 -04:00

136 lines
3.2 KiB
Go

package services
import (
"database/sql"
"errors"
"fmt"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"sync"
"time"
models "vibeStonk/server/models/v1"
"vibeStonk/server/repository"
)
var (
ErrBadCredentials = errors.New("bad authentication credentials")
ErrExpiredCredentials = errors.New("expired credentials")
ErrExistingUser = errors.New("username taken")
)
func NewAuthService(config *repository.Config) (AuthService, error) {
db, err := repository.GetSystemConnector(config)
if err != nil {
return nil, fmt.Errorf("failed to initialize DB connection for auth service: %w", err)
}
users, err := repository.NewUserRepo(config, db)
if err != nil {
return nil, fmt.Errorf("failed to initialize the user repository while creating auth service: %w", err)
}
sessions, err := repository.NewSessionRepo(config, db)
return &authService{
db: db,
lock: &sync.Mutex{},
sessions: sessions,
users: users,
}, nil
}
type AuthService interface {
RegisterUser(username string, prefname string, password []byte) (*models.User, error)
// UpdateUser(user *models.User)(*models.User, error
AuthenticateUser(username string, password []byte) (*models.Session, error)
AuthenticateToken(tokenValue string) (*models.User, error)
Close() error
}
type authService struct {
db *sql.DB
lock *sync.Mutex
sessions repository.SessionRepo
users repository.UserRepo
}
func (a *authService) RegisterUser(username string, prefname string, password []byte) (*models.User, error) {
a.lock.Lock()
defer a.lock.Unlock()
id := uuid.New().String()
// Generate password hash using bcrypt
hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
user := &models.User{
Id: id,
UserName: username,
PrefName: prefname,
Hash: string(hashedPassword),
}
eUser, err := a.users.GetByUsername(user.UserName)
if err == nil || eUser != nil {
return nil, ErrExistingUser
}
user, err = a.users.Create(user)
if err != nil {
return nil, err
}
return user, nil
}
func (a *authService) AuthenticateUser(username string, password []byte) (*models.Session, error) {
user, err := a.users.GetByUsername(username)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
err = bcrypt.CompareHashAndPassword([]byte(user.Hash), password)
if err != nil {
return nil, ErrBadCredentials
}
session, err := a.sessions.Create(user)
if err != nil {
return nil, fmt.Errorf("failed to create session for user: %w", err)
}
return session, nil
}
func (a *authService) AuthenticateToken(tokenValue string) (*models.User, error) {
session, err := a.sessions.Get(tokenValue)
if err != nil || session.Revoked {
return nil, ErrBadCredentials
}
if time.Now().After(session.Expires.AsTime()) {
return nil, ErrExpiredCredentials
}
user, err := a.users.Get(session.UserID)
if err != nil {
// this means that we couldn't find a valid user associated with the session
// this shouldn't really ever happen
return nil, ErrBadCredentials
}
user.Hash = ""
return user, nil
}
func (a *authService) Close() error {
err := a.db.Close()
if err != nil {
return err
}
return nil
}