package gateway import ( "context" "time" "github.com/google/uuid" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service" gatewayuc "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/gateway" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/jwt" ) // LoginService handles user login operations type LoginService interface { Login(ctx context.Context, input *LoginInput) (*LoginResponse, error) } // LoginInput represents the input for user login type LoginInput struct { Email string Password string } // LoginResponse represents the response after successful login type LoginResponse struct { // User details UserID string `json:"user_id"` UserEmail string `json:"user_email"` UserName string `json:"user_name"` UserRole string `json:"user_role"` // Tenant details TenantID string `json:"tenant_id"` // Session and tokens SessionID string `json:"session_id"` AccessToken string `json:"access_token"` AccessExpiry time.Time `json:"access_expiry"` RefreshToken string `json:"refresh_token"` RefreshExpiry time.Time `json:"refresh_expiry"` LoginAt time.Time `json:"login_at"` } type loginService struct { loginUC *gatewayuc.LoginUseCase sessionService service.SessionService jwtProvider jwt.Provider logger *zap.Logger } // NewLoginService creates a new login service func NewLoginService( loginUC *gatewayuc.LoginUseCase, sessionService service.SessionService, jwtProvider jwt.Provider, logger *zap.Logger, ) LoginService { return &loginService{ loginUC: loginUC, sessionService: sessionService, jwtProvider: jwtProvider, logger: logger.Named("login-service"), } } // Login handles the complete login flow func (s *loginService) Login(ctx context.Context, input *LoginInput) (*LoginResponse, error) { // CWE-532: Use hashed email to prevent PII in logs s.logger.Info("processing login request", logger.EmailHash(input.Email)) // Execute login use case (validates credentials) loginOutput, err := s.loginUC.Execute(ctx, &gatewayuc.LoginInput{ Email: input.Email, Password: input.Password, }) if err != nil { s.logger.Error("login failed", zap.Error(err)) return nil, err } // CWE-532: Use hashed email to prevent PII in logs s.logger.Info("credentials validated successfully", zap.String("user_id", loginOutput.UserID), logger.EmailHash(loginOutput.UserEmail), zap.String("tenant_id", loginOutput.TenantID)) // Parse tenant ID to UUID tenantUUID, err := uuid.Parse(loginOutput.TenantID) if err != nil { s.logger.Error("failed to parse tenant ID", zap.Error(err)) return nil, err } // Parse user ID to UUID userUUID, err := uuid.Parse(loginOutput.UserID) if err != nil { s.logger.Error("failed to parse user ID", zap.Error(err)) return nil, err } // CWE-384: Invalidate all existing sessions before creating new one (Session Fixation Prevention) // This ensures that any session IDs an attacker may have obtained are invalidated s.logger.Info("invalidating existing sessions for security", zap.String("user_uuid", userUUID.String())) if err := s.sessionService.InvalidateUserSessions(ctx, userUUID); err != nil { // Log warning but don't fail login - this is best effort cleanup s.logger.Warn("failed to invalidate existing sessions (non-fatal)", zap.String("user_uuid", userUUID.String()), zap.Error(err)) } // Create new session in two-tier cache session, err := s.sessionService.CreateSession( ctx, 0, // UserID as uint64 - not used in our UUID-based system userUUID, loginOutput.UserEmail, loginOutput.UserName, loginOutput.UserRole, tenantUUID, ) if err != nil { s.logger.Error("failed to create session", zap.Error(err)) return nil, err } s.logger.Info("session created", zap.String("session_id", session.ID)) // Generate JWT access and refresh tokens accessToken, accessExpiry, refreshToken, refreshExpiry, err := s.jwtProvider.GenerateTokenPair( session.ID, AccessTokenDuration, RefreshTokenDuration, ) if err != nil { s.logger.Error("failed to generate tokens", zap.Error(err)) // Clean up session _ = s.sessionService.DeleteSession(ctx, session.ID) return nil, err } s.logger.Info("login completed successfully", zap.String("user_id", loginOutput.UserID), zap.String("tenant_id", loginOutput.TenantID), zap.String("session_id", session.ID)) return &LoginResponse{ UserID: loginOutput.UserID, UserEmail: loginOutput.UserEmail, UserName: loginOutput.UserName, UserRole: loginOutput.UserRole, TenantID: loginOutput.TenantID, SessionID: session.ID, AccessToken: accessToken, AccessExpiry: accessExpiry, RefreshToken: refreshToken, RefreshExpiry: refreshExpiry, LoginAt: time.Now().UTC(), }, nil }