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 }