144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"git.threesix.ai/jordan/slack-auth-1770277926/pkg/auth"
|
|
"git.threesix.ai/jordan/slack-auth-1770277926/pkg/logging"
|
|
"git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/domain"
|
|
"git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/port"
|
|
)
|
|
|
|
// UserService handles user-related business logic.
|
|
type UserService struct {
|
|
repo port.UserRepository
|
|
jwtSecret []byte
|
|
jwtIssuer string
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewUserService creates a new user service.
|
|
func NewUserService(repo port.UserRepository, jwtSecret []byte, jwtIssuer string, logger *logging.Logger) *UserService {
|
|
return &UserService{
|
|
repo: repo,
|
|
jwtSecret: jwtSecret,
|
|
jwtIssuer: jwtIssuer,
|
|
logger: logger.WithService("UserService"),
|
|
}
|
|
}
|
|
|
|
// RegisterInput contains the data needed to register a user.
|
|
type RegisterInput struct {
|
|
Email string
|
|
Password string
|
|
}
|
|
|
|
// RegisterOutput contains the result of user registration.
|
|
type RegisterOutput struct {
|
|
User *domain.User
|
|
Token string
|
|
}
|
|
|
|
// Register creates a new user with duplicate detection.
|
|
// Returns domain.ErrDuplicateUser if email already exists.
|
|
// Returns domain.ErrInvalidEmail if email is invalid.
|
|
// Returns domain.ErrInvalidPassword if password is invalid.
|
|
func (s *UserService) Register(ctx context.Context, input RegisterInput) (*RegisterOutput, error) {
|
|
// Check for duplicates
|
|
exists, err := s.repo.ExistsByEmail(ctx, input.Email)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if exists {
|
|
return nil, domain.ErrDuplicateUser
|
|
}
|
|
|
|
// Generate new ID
|
|
id := domain.UserID(uuid.New().String())
|
|
|
|
// Create domain entity (validates email/password and hashes password)
|
|
user, err := domain.NewUser(id, input.Email, input.Password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Persist
|
|
if err := s.repo.Create(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := s.generateToken(user)
|
|
if err != nil {
|
|
s.logger.Error("failed to generate token after registration", "error", err, "user_id", id)
|
|
return nil, err
|
|
}
|
|
|
|
s.logger.Info("user registered", "id", id, "email", input.Email)
|
|
return &RegisterOutput{
|
|
User: user,
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// LoginInput contains the data needed to authenticate a user.
|
|
type LoginInput struct {
|
|
Email string
|
|
Password string
|
|
}
|
|
|
|
// LoginOutput contains the result of user authentication.
|
|
type LoginOutput struct {
|
|
User *domain.User
|
|
Token string
|
|
}
|
|
|
|
// Login authenticates a user and returns a JWT token.
|
|
// Returns domain.ErrInvalidCredentials if credentials are incorrect.
|
|
func (s *UserService) Login(ctx context.Context, input LoginInput) (*LoginOutput, error) {
|
|
// Find user by email
|
|
user, err := s.repo.GetByEmail(ctx, input.Email)
|
|
if err != nil {
|
|
if err == domain.ErrUserNotFound {
|
|
return nil, domain.ErrInvalidCredentials
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Verify password
|
|
if !user.CheckPassword(input.Password) {
|
|
return nil, domain.ErrInvalidCredentials
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := s.generateToken(user)
|
|
if err != nil {
|
|
s.logger.Error("failed to generate token", "error", err, "user_id", user.ID)
|
|
return nil, err
|
|
}
|
|
|
|
s.logger.Info("user logged in", "id", user.ID, "email", input.Email)
|
|
return &LoginOutput{
|
|
User: user,
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// GetByID returns a user by ID.
|
|
// Returns domain.ErrUserNotFound if not found.
|
|
func (s *UserService) GetByID(ctx context.Context, id domain.UserID) (*domain.User, error) {
|
|
return s.repo.Get(ctx, id)
|
|
}
|
|
|
|
// generateToken creates a JWT token for the user.
|
|
func (s *UserService) generateToken(user *domain.User) (string, error) {
|
|
authUser := &auth.User{
|
|
ID: user.ID.String(),
|
|
Email: user.Email,
|
|
}
|
|
return auth.GenerateTokenWithIssuer(s.jwtSecret, authUser, 24*time.Hour, s.jwtIssuer, s.jwtIssuer)
|
|
}
|