mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 02:42:24 -06:00 
			
		
		
		
	* catch SQLITE_BUSY errors, wrap bun.DB to use our own busy retrier, remove unnecessary db.Error type Signed-off-by: kim <grufwub@gmail.com> * remove dead code Signed-off-by: kim <grufwub@gmail.com> * remove more dead code, add missing error arguments Signed-off-by: kim <grufwub@gmail.com> * update sqlite to use maxOpenConns() Signed-off-by: kim <grufwub@gmail.com> * add uncommitted changes Signed-off-by: kim <grufwub@gmail.com> * use direct calls-through for the ConnIface to make sure we don't double query hook Signed-off-by: kim <grufwub@gmail.com> * expose underlying bun.DB better Signed-off-by: kim <grufwub@gmail.com> * retry on the correct busy error Signed-off-by: kim <grufwub@gmail.com> * use longer possible maxRetries for db retry-backoff Signed-off-by: kim <grufwub@gmail.com> * remove the note regarding max-open-conns only applying to postgres Signed-off-by: kim <grufwub@gmail.com> * improved code commenting Signed-off-by: kim <grufwub@gmail.com> * remove unnecessary infof call (just use info) Signed-off-by: kim <grufwub@gmail.com> * rename DBConn to WrappedDB to better follow sql package name conventions Signed-off-by: kim <grufwub@gmail.com> * update test error string checks Signed-off-by: kim <grufwub@gmail.com> * shush linter Signed-off-by: kim <grufwub@gmail.com> * update backoff logic to be more transparent Signed-off-by: kim <grufwub@gmail.com> --------- Signed-off-by: kim <grufwub@gmail.com>
		
			
				
	
	
		
			119 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			119 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// GoToSocial
 | 
						|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package middleware
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/log"
 | 
						|
 | 
						|
	"github.com/gin-gonic/gin"
 | 
						|
	"github.com/go-fed/httpsig"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	sigHeader  = string(httpsig.Signature)
 | 
						|
	authHeader = string(httpsig.Authorization)
 | 
						|
	// untyped error returned by httpsig when no signature is present
 | 
						|
	noSigError = "neither \"" + sigHeader + "\" nor \"" + authHeader + "\" have signature parameters"
 | 
						|
)
 | 
						|
 | 
						|
// SignatureCheck returns a gin middleware for checking http signatures.
 | 
						|
//
 | 
						|
// The middleware first checks whether an incoming http request has been
 | 
						|
// http-signed with a well-formed signature. If so, it will check if the
 | 
						|
// domain that signed the request is permitted to access the server, using
 | 
						|
// the provided uriBlocked function. If the domain is blocked, the middleware
 | 
						|
// will abort the request chain with http code 403 forbidden. If it is not
 | 
						|
// blocked, the handler will set the key verifier and the signature in the
 | 
						|
// context for use down the line.
 | 
						|
//
 | 
						|
// In case of an error, the request will be aborted with http code 500.
 | 
						|
func SignatureCheck(uriBlocked func(context.Context, *url.URL) (bool, error)) func(*gin.Context) {
 | 
						|
	return func(c *gin.Context) {
 | 
						|
		ctx := c.Request.Context()
 | 
						|
 | 
						|
		// Create the signature verifier from the request;
 | 
						|
		// this will error if the request wasn't signed.
 | 
						|
		verifier, err := httpsig.NewVerifier(c.Request)
 | 
						|
		if err != nil {
 | 
						|
			// Only actually *abort* the request with 401
 | 
						|
			// if a signature was present but malformed.
 | 
						|
			// Otherwise proceed with an unsigned request;
 | 
						|
			// it's up to other functions to reject this.
 | 
						|
			if err.Error() != noSigError {
 | 
						|
				log.Debugf(ctx, "http signature was present but invalid: %s", err)
 | 
						|
				c.AbortWithStatus(http.StatusUnauthorized)
 | 
						|
			}
 | 
						|
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// The request was signed! The key ID should be given
 | 
						|
		// in the signature so that we know where to fetch it
 | 
						|
		// from the remote server. This will be something like:
 | 
						|
		// https://example.org/users/some_remote_user#main-key
 | 
						|
		pubKeyIDStr := verifier.KeyId()
 | 
						|
 | 
						|
		// Key can sometimes be nil, according to url parse
 | 
						|
		// func: 'Trying to parse a hostname and path without
 | 
						|
		// a scheme is invalid but may not necessarily return
 | 
						|
		// an error, due to parsing ambiguities'. Catch this.
 | 
						|
		pubKeyID, err := url.Parse(pubKeyIDStr)
 | 
						|
		if err != nil || pubKeyID == nil {
 | 
						|
			log.Warnf(ctx, "pubkey id %s could not be parsed as a url", pubKeyIDStr)
 | 
						|
			c.AbortWithStatus(http.StatusUnauthorized)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// If the domain is blocked we want to bail as fast as
 | 
						|
		// possible without the request proceeding further.
 | 
						|
		blocked, err := uriBlocked(ctx, pubKeyID)
 | 
						|
		if err != nil {
 | 
						|
			log.Errorf(ctx, "error checking block for domain %s: %s", pubKeyID.Host, err)
 | 
						|
			c.AbortWithStatus(http.StatusInternalServerError)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		if blocked {
 | 
						|
			log.Infof(ctx, "domain %s is blocked", pubKeyID.Host)
 | 
						|
			c.AbortWithStatus(http.StatusForbidden)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// Assume signature was set on Signature header,
 | 
						|
		// but fall back to Authorization header if necessary.
 | 
						|
		signature := c.GetHeader(sigHeader)
 | 
						|
		if signature == "" {
 | 
						|
			signature = c.GetHeader(authHeader)
 | 
						|
		}
 | 
						|
 | 
						|
		// Set relevant values on the request context
 | 
						|
		// to save some work further down the line.
 | 
						|
		ctx = gtscontext.SetHTTPSignatureVerifier(ctx, verifier)
 | 
						|
		ctx = gtscontext.SetHTTPSignature(ctx, signature)
 | 
						|
		ctx = gtscontext.SetHTTPSignaturePubKeyID(ctx, pubKeyID)
 | 
						|
 | 
						|
		// Replace request with a shallow
 | 
						|
		// copy with the new context.
 | 
						|
		c.Request = c.Request.WithContext(ctx)
 | 
						|
	}
 | 
						|
}
 |