Files
2026-01-08 01:15:07 +00:00

140 lines
3.6 KiB
Go

package service
import (
"context"
"fmt"
"time"
"github.com/FernandoJVideira/simple_bank/internal/auth"
"github.com/FernandoJVideira/simple_bank/internal/config"
db "github.com/FernandoJVideira/simple_bank/internal/db/sqlc"
"github.com/FernandoJVideira/simple_bank/internal/security"
)
type authService struct {
store db.Store
tokenMaker auth.Maker
config config.Config
}
// NewAuthService creates a new auth service
func NewAuthService(store db.Store, tokenMaker auth.Maker, config config.Config) AuthService {
return &authService{
store: store,
tokenMaker: tokenMaker,
config: config,
}
}
func (s *authService) Login(ctx context.Context, params LoginCredentials) (*LoginResponse, error) {
// Get user from database
user, err := s.store.GetUser(ctx, params.Username)
if err != nil {
return nil, fmt.Errorf("user not found: %w", err)
}
// Verify password
err = security.CheckPassword(params.Password, user.HashedPassword)
if err != nil {
return nil, fmt.Errorf("incorrect username or password: %w", err)
}
// Create access token
accessToken, accessPayload, err := s.tokenMaker.CreateToken(
user.Username,
user.Role,
s.config.AuthTokenExp,
)
if err != nil {
return nil, fmt.Errorf("failed to create access token: %w", err)
}
// Create refresh token
refreshToken, refreshPayload, err := s.tokenMaker.CreateToken(
user.Username,
user.Role,
s.config.RefreshTokenExp,
)
if err != nil {
return nil, fmt.Errorf("failed to create refresh token: %w", err)
}
// Create session
session, err := s.store.CreateSession(ctx, db.CreateSessionParams{
ID: refreshPayload.ID,
Username: user.Username,
RefreshToken: refreshToken,
UserAgent: params.UserAgent,
ClientIp: params.ClientIP,
IsBlocked: false,
ExpiresAt: refreshPayload.ExpiredAt,
})
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
return &LoginResponse{
SessionID: session.ID,
AccessToken: accessToken,
AccessTokenExpiresAt: accessPayload.ExpiredAt,
RefreshToken: refreshToken,
RefreshTokenExpiresAt: refreshPayload.ExpiredAt,
User: user,
}, nil
}
func (s *authService) RenewAccessToken(ctx context.Context, params TokenRenewalRequest) (*TokenRenewalResponse, error) {
// Verify refresh token
refreshPayload, err := s.tokenMaker.VerifyToken(params.RefreshToken)
if err != nil {
return nil, fmt.Errorf("invalid refresh token: %w", err)
}
// Get session
session, err := s.store.GetSession(ctx, refreshPayload.ID)
if err != nil {
return nil, fmt.Errorf("session not found: %w", err)
}
// Validate session
if err := s.validateSession(session, refreshPayload.Username, params.RefreshToken); err != nil {
return nil, err
}
// Create new access token
accessToken, accessPayload, err := s.tokenMaker.CreateToken(
refreshPayload.Username,
refreshPayload.Role,
s.config.AuthTokenExp,
)
if err != nil {
return nil, fmt.Errorf("failed to create access token: %w", err)
}
return &TokenRenewalResponse{
AccessToken: accessToken,
AccessTokenExpiresAt: accessPayload.ExpiredAt,
}, nil
}
// validateSession contains business rules for session validation
func (s *authService) validateSession(session db.Session, username, refreshToken string) error {
if session.IsBlocked {
return fmt.Errorf("session is blocked")
}
if session.Username != username {
return fmt.Errorf("incorrect session user")
}
if session.RefreshToken != refreshToken {
return fmt.Errorf("mismatched session token")
}
if time.Now().After(session.ExpiresAt) {
return fmt.Errorf("session has expired")
}
return nil
}