package gateway import ( "fmt" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/password" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/securestring" ) // VerifyPasswordUseCase verifies a password against a hash type VerifyPasswordUseCase struct { passwordProvider password.PasswordProvider logger *zap.Logger } // ProvideVerifyPasswordUseCase creates a new VerifyPasswordUseCase func ProvideVerifyPasswordUseCase( passwordProvider password.PasswordProvider, logger *zap.Logger, ) *VerifyPasswordUseCase { return &VerifyPasswordUseCase{ passwordProvider: passwordProvider, logger: logger.Named("verify-password-usecase"), } } // Execute verifies a plain password against a hashed password // Returns ErrInvalidCredentials if password doesn't match (for security) func (uc *VerifyPasswordUseCase) Execute(plainPassword, passwordHash string) error { // Create secure string from password securePassword, err := securestring.NewSecureString(plainPassword) if err != nil { uc.logger.Error("failed to create secure password", zap.Error(err)) return fmt.Errorf("failed to process password: %w", err) } defer securePassword.Wipe() // Clean up password from memory // Verify password match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, passwordHash) if err != nil { uc.logger.Error("failed to compare password and hash", zap.Error(err)) return fmt.Errorf("failed to verify password: %w", err) } if !match { uc.logger.Debug("password verification failed") return ErrInvalidCredentials } uc.logger.Debug("password verified successfully") return nil } // ExecuteTimingSafe verifies a password in a timing-safe manner // CWE-208: Observable Timing Discrepancy - Prevents user enumeration via timing attacks // // This method ALWAYS performs password hashing, even when the user doesn't exist, // to ensure constant-time behavior regardless of whether the email exists in the system. // // Parameters: // - plainPassword: The password to verify // - passwordHash: The hash to compare against (empty string if user doesn't exist) // - userExists: Whether the user exists in the system // // Returns ErrInvalidCredentials if verification fails for any reason func (uc *VerifyPasswordUseCase) ExecuteTimingSafe(plainPassword, passwordHash string, userExists bool) error { // Create secure string from password securePassword, err := securestring.NewSecureString(plainPassword) if err != nil { uc.logger.Error("failed to create secure password", zap.Error(err)) return fmt.Errorf("failed to process password: %w", err) } defer securePassword.Wipe() // Clean up password from memory if !userExists || passwordHash == "" { // User doesn't exist or no password hash available // Perform dummy password hashing to maintain constant time uc.logger.Debug("performing timing-safe dummy password verification") match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, password.DummyPasswordHash) if err != nil { // Even if dummy verification fails, we don't care about the error // The important part is that we spent the same amount of time uc.logger.Debug("dummy password verification completed", zap.Error(err)) } _ = match // Explicitly ignore the result return ErrInvalidCredentials } // User exists - perform real password verification match, err := uc.passwordProvider.ComparePasswordAndHash(securePassword, passwordHash) if err != nil { uc.logger.Error("failed to compare password and hash", zap.Error(err)) return ErrInvalidCredentials } if !match { uc.logger.Debug("password verification failed") return ErrInvalidCredentials } uc.logger.Debug("password verified successfully") return nil }