mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 19:52:24 -05:00 
			
		
		
		
	[feature] Add Mastodon-compatible HTTP signature fallback (#2659)
On outgoing `GET` requests that are signed (e.g. authorized fetch), if the initial request fails with `401`, try again, but _without_ the query parameters included in the HTTP signature. This is primarily useful for compatibility with Mastodon; though hopefully this can be removed in the not-too-distant future, as they've started changing their behavior here. Signed-off-by: Milas Bowman <devnull@milas.dev>
This commit is contained in:
		
					parent
					
						
							
								2db115fa36
							
						
					
				
			
			
				commit
				
					
						af1a26a68f
					
				
			
		
					 3 changed files with 16 additions and 8 deletions
				
			
		|  | @ -136,7 +136,7 @@ func HTTPSignatureVerifier(ctx context.Context) httpsig.VerifierWithOptions { | ||||||
| 
 | 
 | ||||||
| // SetHTTPSignatureVerifier stores the given http signature verifier and returns the | // SetHTTPSignatureVerifier stores the given http signature verifier and returns the | ||||||
| // wrapped context. See HTTPSignatureVerifier() for further information on the verifier value. | // wrapped context. See HTTPSignatureVerifier() for further information on the verifier value. | ||||||
| func SetHTTPSignatureVerifier(ctx context.Context, verifier httpsig.Verifier) context.Context { | func SetHTTPSignatureVerifier(ctx context.Context, verifier httpsig.VerifierWithOptions) context.Context { | ||||||
| 	return context.WithValue(ctx, httpSigVerifierKey, verifier) | 	return context.WithValue(ctx, httpSigVerifierKey, verifier) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,13 +30,13 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewGETSigner returns a new httpsig.Signer instance initialized with GTS GET preferences. | // NewGETSigner returns a new httpsig.Signer instance initialized with GTS GET preferences. | ||||||
| func NewGETSigner(expiresIn int64) (httpsig.Signer, error) { | func NewGETSigner(expiresIn int64) (httpsig.SignerWithOptions, error) { | ||||||
| 	sig, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature, expiresIn) | 	sig, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature, expiresIn) | ||||||
| 	return sig, err | 	return sig, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewPOSTSigner returns a new httpsig.Signer instance initialized with GTS POST preferences. | // NewPOSTSigner returns a new httpsig.Signer instance initialized with GTS POST preferences. | ||||||
| func NewPOSTSigner(expiresIn int64) (httpsig.Signer, error) { | func NewPOSTSigner(expiresIn int64) (httpsig.SignerWithOptions, error) { | ||||||
| 	sig, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature, expiresIn) | 	sig, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature, expiresIn) | ||||||
| 	return sig, err | 	return sig, err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -84,8 +84,8 @@ type transport struct { | ||||||
| 	privkey    crypto.PrivateKey | 	privkey    crypto.PrivateKey | ||||||
| 
 | 
 | ||||||
| 	signerExp  time.Time | 	signerExp  time.Time | ||||||
| 	getSigner  httpsig.Signer | 	getSigner  httpsig.SignerWithOptions | ||||||
| 	postSigner httpsig.Signer | 	postSigner httpsig.SignerWithOptions | ||||||
| 	signerMu   sync.Mutex | 	signerMu   sync.Mutex | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -97,7 +97,15 @@ func (t *transport) GET(r *http.Request) (*http.Response, error) { | ||||||
| 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID) | 	ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID) | ||||||
| 	r = r.WithContext(ctx) // replace request ctx. | 	r = r.WithContext(ctx) // replace request ctx. | ||||||
| 	r.Header.Set("User-Agent", t.controller.userAgent) | 	r.Header.Set("User-Agent", t.controller.userAgent) | ||||||
| 	return t.controller.client.DoSigned(r, t.signGET()) | 
 | ||||||
|  | 	resp, err := t.controller.client.DoSigned(r, t.signGET(httpsig.SignatureOption{ExcludeQueryStringFromPathPseudoHeader: false})) | ||||||
|  | 	if err != nil || resp.StatusCode != http.StatusUnauthorized { | ||||||
|  | 		return resp, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// try again without the path included in the HTTP signature for better compatibility | ||||||
|  | 	_ = resp.Body.Close() | ||||||
|  | 	return t.controller.client.DoSigned(r, t.signGET(httpsig.SignatureOption{ExcludeQueryStringFromPathPseudoHeader: true})) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) { | func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) { | ||||||
|  | @ -112,10 +120,10 @@ func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // signGET will safely sign an HTTP GET request. | // signGET will safely sign an HTTP GET request. | ||||||
| func (t *transport) signGET() httpclient.SignFunc { | func (t *transport) signGET(opts httpsig.SignatureOption) httpclient.SignFunc { | ||||||
| 	return func(r *http.Request) (err error) { | 	return func(r *http.Request) (err error) { | ||||||
| 		t.safesign(func() { | 		t.safesign(func() { | ||||||
| 			err = t.getSigner.SignRequest(t.privkey, t.pubKeyID, r, nil) | 			err = t.getSigner.SignRequestWithOptions(t.privkey, t.pubKeyID, r, nil, opts) | ||||||
| 		}) | 		}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue