summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Milas Bowman <devnull@milas.dev>2024-02-19 05:18:17 -0500
committerLibravatar GitHub <noreply@github.com>2024-02-19 11:18:17 +0100
commitaf1a26a68f9c912616eeb56bc8a4f52ba2210ae2 (patch)
treee0dbe7799ccb3f5b611d82471e285bce641b5618
parent[chore]: Bump github.com/minio/minio-go/v7 from 7.0.66 to 7.0.67 (#2662) (diff)
downloadgotosocial-af1a26a68f9c912616eeb56bc8a4f52ba2210ae2.tar.xz
[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>
-rw-r--r--internal/gtscontext/context.go2
-rw-r--r--internal/transport/signing.go4
-rw-r--r--internal/transport/transport.go18
3 files changed, 16 insertions, 8 deletions
diff --git a/internal/gtscontext/context.go b/internal/gtscontext/context.go
index 0d5ed5340..73b82744a 100644
--- a/internal/gtscontext/context.go
+++ b/internal/gtscontext/context.go
@@ -136,7 +136,7 @@ func HTTPSignatureVerifier(ctx context.Context) httpsig.VerifierWithOptions {
// SetHTTPSignatureVerifier stores the given http signature verifier and returns the
// 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)
}
diff --git a/internal/transport/signing.go b/internal/transport/signing.go
index dcd8e206f..e33e4a05f 100644
--- a/internal/transport/signing.go
+++ b/internal/transport/signing.go
@@ -30,13 +30,13 @@ var (
)
// 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)
return sig, err
}
// 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)
return sig, err
}
diff --git a/internal/transport/transport.go b/internal/transport/transport.go
index 558e187f0..a2a9dc23c 100644
--- a/internal/transport/transport.go
+++ b/internal/transport/transport.go
@@ -84,8 +84,8 @@ type transport struct {
privkey crypto.PrivateKey
signerExp time.Time
- getSigner httpsig.Signer
- postSigner httpsig.Signer
+ getSigner httpsig.SignerWithOptions
+ postSigner httpsig.SignerWithOptions
signerMu sync.Mutex
}
@@ -97,7 +97,15 @@ func (t *transport) GET(r *http.Request) (*http.Response, error) {
ctx = gtscontext.SetOutgoingPublicKeyID(ctx, t.pubKeyID)
r = r.WithContext(ctx) // replace request ctx.
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) {
@@ -112,10 +120,10 @@ func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) {
}
// 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) {
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
}