Igris/internal/usecase/auth_service.go

130 lines
3.3 KiB
Go

package usecase
import (
"backend/config"
"backend/internal/domain"
"backend/pkg/jwt"
"backend/pkg/validate/common"
"context"
"errors"
"fmt"
"time"
jwt2 "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
type AuthService interface {
GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error)
Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error)
}
type authService struct {
userRepo domain.UserRepo
sessionRepo domain.SessionRepo
challengeExp uint
cfg config.JWT
}
type UserToken struct {
AuthorizationToken string
RefreshToken string
ExpiresAt int64
}
func NewAuthService(
userRepo domain.UserRepo,
sessionRepo domain.SessionRepo,
challengeExp uint,
cfg config.JWT,
) AuthService {
return &authService{
userRepo: userRepo,
sessionRepo: sessionRepo,
challengeExp: challengeExp,
cfg: cfg,
}
}
func (s *authService) GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) {
_, err := common.IsValidPublicKey(pubKey)
if err != nil {
return nil, err
}
challenge := &domain.Challenge{
Message: uuid.New(),
TimeStamp: time.Now().UTC(),
ExpiresAt: time.Now().Add(time.Duration(s.challengeExp) * time.Minute),
}
return challenge, nil
}
func (s *authService) Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error) {
_, err := common.IsValidPublicKey(pubKey)
if err != nil {
return nil, fmt.Errorf("invalid public key: %w", err)
}
isValid, err := challenge.Verify(pubKey, signature)
if err != nil {
return nil, fmt.Errorf("signature verification failed: %w", err)
}
if !isValid {
return nil, errors.New("invalid signature")
}
user, err := s.userRepo.GetByPubKey(ctx, pubKey)
if err != nil {
return nil, err
}
if user == nil {
return nil, domain.ErrUserNotFound
}
user.UpdateLastLogin()
if err := s.userRepo.Update(ctx, user); err != nil {
return nil, err
}
expiresAt := time.Now().Add(time.Duration(s.cfg.TokenExpMinutes) * time.Minute)
session := domain.NewSession(user.ID, user.PubKey, expiresAt)
err = s.sessionRepo.Create(ctx, session)
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
claims := &jwt.UserClaims{
UserID: uint(user.ID.ID()),
Sections: []string{},
}
claims.ExpiresAt = jwt2.NewNumericDate(expiresAt)
claims.IssuedAt = jwt2.NewNumericDate(time.Now())
authToken, err := jwt.CreateToken([]byte(s.cfg.TokenSecret), claims)
if err != nil {
return nil, err
}
refreshExpiresAt := time.Now().Add(time.Duration(s.cfg.RefreshTokenExpMinutes) * time.Minute)
refreshClaims := &jwt.UserClaims{
UserID: uint(user.ID.ID()),
Sections: []string{"refresh"},
}
refreshClaims.ExpiresAt = jwt2.NewNumericDate(refreshExpiresAt)
refreshClaims.IssuedAt = jwt2.NewNumericDate(time.Now())
refreshToken, err := jwt.CreateToken([]byte(s.cfg.TokenSecret), refreshClaims)
if err != nil {
return nil, fmt.Errorf("failed to create refresh token: %w", err)
}
return &UserToken{
AuthorizationToken: authToken,
RefreshToken: refreshToken,
ExpiresAt: expiresAt.Unix(),
}, nil
}