some lil fixes for kibou compatibility
This commit is contained in:
		@ -47,12 +47,13 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) {
 | 
			
		||||
			c.JSON(withCode.Code(), withCode.Safe())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		l.Debug(err)
 | 
			
		||||
		l.Debugf("InboxPOSTHandler: error processing request: %s", err)
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !posted {
 | 
			
		||||
		l.Debugf("request could not be handled as an AP request; headers were: %+v", c.Request.Header)
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,42 +24,53 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org
 | 
			
		||||
func (m *Module) WebfingerGETRequest(c *gin.Context) {
 | 
			
		||||
	l := m.log.WithFields(logrus.Fields{
 | 
			
		||||
		"func": "WebfingerGETRequest",
 | 
			
		||||
		"user-agent": c.Request.UserAgent(),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	q, set := c.GetQuery("resource")
 | 
			
		||||
	if !set || q == "" {
 | 
			
		||||
		l.Debug("aborting request because no resource was set in query")
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	withAcct := strings.Split(q, "acct:")
 | 
			
		||||
	if len(withAcct) != 2 {
 | 
			
		||||
		l.Debugf("aborting request because resource query %s could not be split by 'acct:'", q)
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	usernameDomain := strings.Split(withAcct[1], "@")
 | 
			
		||||
	if len(usernameDomain) != 2 {
 | 
			
		||||
		l.Debugf("aborting request because username and domain could not be parsed from %s", withAcct[1])
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	username := strings.ToLower(usernameDomain[0])
 | 
			
		||||
	domain := strings.ToLower(usernameDomain[1])
 | 
			
		||||
	if username == "" || domain == "" {
 | 
			
		||||
		l.Debug("aborting request because username or domain was empty")
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if domain != m.config.Host {
 | 
			
		||||
		l.Debug("aborting request because domain %s does not belong to this instance", domain)
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("domain %s does not belong to this instance", domain)})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := m.processor.GetWebfingerAccount(username, c.Request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		l.Debugf("aborting request with an error: %s", err.Error())
 | 
			
		||||
		c.JSON(err.Code(), gin.H{"error": err.Safe()})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								internal/api/security/robots.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								internal/api/security/robots.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package security
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const robotsString = `User-agent: *
 | 
			
		||||
Disallow: /
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
// RobotsGETHandler returns the most restrictive possible robots.txt file in response to a call to /robots.txt.
 | 
			
		||||
// The response instructs bots with *any* user agent not to index the instance at all.
 | 
			
		||||
func (m *Module) RobotsGETHandler(c *gin.Context) {
 | 
			
		||||
	c.String(http.StatusOK, robotsString)
 | 
			
		||||
}
 | 
			
		||||
@ -19,12 +19,16 @@
 | 
			
		||||
package security
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/api"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/config"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/router"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const robotsPath = "/robots.txt"
 | 
			
		||||
 | 
			
		||||
// Module implements the ClientAPIModule interface for security middleware
 | 
			
		||||
type Module struct {
 | 
			
		||||
	config *config.Config
 | 
			
		||||
@ -44,5 +48,6 @@ func (m *Module) Route(s router.Router) error {
 | 
			
		||||
	s.AttachMiddleware(m.FlocBlock)
 | 
			
		||||
	s.AttachMiddleware(m.ExtraHeaders)
 | 
			
		||||
	s.AttachMiddleware(m.UserAgentBlock)
 | 
			
		||||
	s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,20 +23,24 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserAgentBlock is a middleware that prevents google chrome cohort tracking by
 | 
			
		||||
// writing the Permissions-Policy header after all other parts of the request have been completed.
 | 
			
		||||
// See: https://plausible.io/blog/google-floc
 | 
			
		||||
// UserAgentBlock blocks requests with undesired, empty, or invalid user-agent strings.
 | 
			
		||||
func (m *Module) UserAgentBlock(c *gin.Context) {
 | 
			
		||||
	l := m.log.WithFields(logrus.Fields{
 | 
			
		||||
		"func": "UserAgentBlock",
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	ua := c.Request.UserAgent()
 | 
			
		||||
	if ua == "" {
 | 
			
		||||
		l.Debug("aborting request because there's no user-agent set")
 | 
			
		||||
		c.AbortWithStatus(http.StatusTeapot)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(strings.ToLower(c.Request.UserAgent()), strings.ToLower("friendica")) {
 | 
			
		||||
	if strings.Contains(strings.ToLower(ua), strings.ToLower("friendica")) {
 | 
			
		||||
		l.Debugf("aborting request with user-agent %s because it contains 'friendica'", ua)
 | 
			
		||||
		c.AbortWithStatus(http.StatusTeapot)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"github.com/go-fed/activity/streams"
 | 
			
		||||
	"github.com/go-fed/activity/streams/vocab"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/db"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/util"
 | 
			
		||||
)
 | 
			
		||||
@ -58,7 +59,43 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() {
 | 
			
		||||
		// check if the object is an IRI
 | 
			
		||||
		if iter.IsIRI() {
 | 
			
		||||
			// we have just the URI of whatever is being accepted, so we need to find out what it is
 | 
			
		||||
			acceptedObjectIRI := iter.GetIRI()
 | 
			
		||||
			if util.IsFollowPath(acceptedObjectIRI) {
 | 
			
		||||
				// ACCEPT FOLLOW
 | 
			
		||||
				gtsFollowRequest := >smodel.FollowRequest{}
 | 
			
		||||
				if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: acceptedObjectIRI.String()}}, gtsFollowRequest); err != nil {
 | 
			
		||||
					return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", acceptedObjectIRI.String(), err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// make sure the addressee of the original follow is the same as whatever inbox this landed in
 | 
			
		||||
				if gtsFollowRequest.AccountID != inboxAcct.ID {
 | 
			
		||||
					return errors.New("ACCEPT: follow object account and inbox account were not the same")
 | 
			
		||||
				}
 | 
			
		||||
				follow, err := f.db.AcceptFollowRequest(gtsFollowRequest.AccountID, gtsFollowRequest.TargetAccountID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				fromFederatorChan <- gtsmodel.FromFederator{
 | 
			
		||||
					APObjectType:     gtsmodel.ActivityStreamsFollow,
 | 
			
		||||
					APActivityType:   gtsmodel.ActivityStreamsAccept,
 | 
			
		||||
					GTSModel:         follow,
 | 
			
		||||
					ReceivingAccount: inboxAcct,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check if iter is an AP object / type
 | 
			
		||||
		if iter.GetType() == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		switch iter.GetType().GetTypeName() {
 | 
			
		||||
			// we have the whole object so we can figure out what we're accepting
 | 
			
		||||
		case string(gtsmodel.ActivityStreamsFollow):
 | 
			
		||||
			// ACCEPT FOLLOW
 | 
			
		||||
			asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)
 | 
			
		||||
 | 
			
		||||
@ -164,7 +164,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
 | 
			
		||||
			return fmt.Errorf("could not convert Like to fave: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newID, err := id.NewULIDFromTime(fave.CreatedAt)
 | 
			
		||||
		newID, err := id.NewULID()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,9 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() {
 | 
			
		||||
		if iter.GetType() == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		switch iter.GetType().GetTypeName() {
 | 
			
		||||
		case string(gtsmodel.ActivityStreamsFollow):
 | 
			
		||||
			// UNDO FOLLOW
 | 
			
		||||
 | 
			
		||||
@ -34,15 +34,6 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
 | 
			
		||||
	l := p.log.WithFields(logrus.Fields{
 | 
			
		||||
		"func":    "HomeTimelineGet",
 | 
			
		||||
		"maxID":   maxID,
 | 
			
		||||
		"sinceID": sinceID,
 | 
			
		||||
		"minID":   minID,
 | 
			
		||||
		"limit":   limit,
 | 
			
		||||
		"local":   local,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	resp := &apimodel.StatusTimelineResponse{
 | 
			
		||||
		Statuses: []*apimodel.Status{},
 | 
			
		||||
	}
 | 
			
		||||
@ -53,9 +44,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
 | 
			
		||||
	sinceIDMarker := sinceID
 | 
			
		||||
	minIDMarker := minID
 | 
			
		||||
 | 
			
		||||
	l.Debugf("\n entering grabloop \n")
 | 
			
		||||
 | 
			
		||||
	l.Debugf("\n querying the db \n")
 | 
			
		||||
	gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if _, ok := err.(db.ErrNoEntries); !ok {
 | 
			
		||||
@ -64,18 +52,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, gtsStatus := range gtsStatuses {
 | 
			
		||||
		// haveAlready := false
 | 
			
		||||
		// for _, apiStatus := range apiStatuses {
 | 
			
		||||
		// 	if apiStatus.ID == gtsStatus.ID {
 | 
			
		||||
		// 		haveAlready = true
 | 
			
		||||
		// 		break
 | 
			
		||||
		// 	}
 | 
			
		||||
		// }
 | 
			
		||||
		// if haveAlready {
 | 
			
		||||
		// 	l.Debugf("\n we have status with id %d already so continuing past this iteration of the loop \n", gtsStatus.ID)
 | 
			
		||||
		// 	continue
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		// pull relevant accounts from the status -- we need this both for checking visibility and for serializing
 | 
			
		||||
		relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -103,7 +79,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			l.Debug("\n appending to the statuses slice \n")
 | 
			
		||||
			apiStatuses = append(apiStatuses, apiStatus)
 | 
			
		||||
			sort.Slice(apiStatuses, func(i int, j int) bool {
 | 
			
		||||
				is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt)
 | 
			
		||||
@ -120,7 +95,6 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			if len(apiStatuses) == limit {
 | 
			
		||||
				l.Debugf("\n we have enough statuses, returning \n")
 | 
			
		||||
				// we have enough
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
@ -143,7 +117,7 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
 | 
			
		||||
			Host:     p.config.Host,
 | 
			
		||||
			Path:     "/api/v1/timelines/home",
 | 
			
		||||
			RawPath:  url.PathEscape("api/v1/timelines/home"),
 | 
			
		||||
			RawQuery: url.QueryEscape(fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID)),
 | 
			
		||||
			RawQuery: fmt.Sprintf("limit=%d&max_id=%s", limit, apiStatuses[len(apiStatuses)-1].ID),
 | 
			
		||||
		}
 | 
			
		||||
		next := fmt.Sprintf("<%s>; rel=\"next\"", nextLink.String())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -117,10 +117,14 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo
 | 
			
		||||
 | 
			
		||||
	// url property
 | 
			
		||||
	url, err := extractURL(accountable)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("could not extract url for person with id %s: %s", uri.String(), err)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// take the URL if we can find it
 | 
			
		||||
		acct.URL = url.String()
 | 
			
		||||
	} else {
 | 
			
		||||
		// otherwise just take the account URI as the URL
 | 
			
		||||
		acct.URL = uri.String()
 | 
			
		||||
	}
 | 
			
		||||
	acct.URL = url.String()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// InboxURI
 | 
			
		||||
	if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil {
 | 
			
		||||
 | 
			
		||||
@ -85,6 +85,11 @@ var (
 | 
			
		||||
	// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following
 | 
			
		||||
	followingPathRegex = regexp.MustCompile(followingPathRegexString)
 | 
			
		||||
 | 
			
		||||
	followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString)
 | 
			
		||||
	// followPathRegex parses a path that validates and captures the username part and the ulid part
 | 
			
		||||
	// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH
 | 
			
		||||
	followPathRegex = regexp.MustCompile(followPathRegexString)
 | 
			
		||||
 | 
			
		||||
	ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}`
 | 
			
		||||
 | 
			
		||||
	likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath)
 | 
			
		||||
 | 
			
		||||
@ -189,6 +189,11 @@ func IsFollowingPath(id *url.URL) bool {
 | 
			
		||||
	return followingPathRegex.MatchString(id.Path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW
 | 
			
		||||
func IsFollowPath(id *url.URL) bool {
 | 
			
		||||
	return followPathRegex.MatchString(id.Path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked
 | 
			
		||||
func IsLikedPath(id *url.URL) bool {
 | 
			
		||||
	return likedPathRegex.MatchString(id.Path)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user