136 lines
3.2 KiB
Go
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
|
|
}
|