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 }