@@ -4,46 +4,50 @@ import (
4
4
"context"
5
5
"crypto"
6
6
"crypto/rsa"
7
- "crypto/x509"
8
- "encoding/pem"
9
- "errors"
10
7
"fmt"
11
8
"net/http"
9
+ "os"
12
10
13
11
"github.com/carlmjohnson/requests"
14
12
"github.com/davecheney/pub/internal/httpsig"
15
- "github.com/davecheney/pub/models "
13
+ "github.com/go-json-experiment/json "
16
14
)
17
15
16
+ // ClientKey is the key used to store the ActivityPub client in the context.
17
+ var ClientKey = struct {}{}
18
+
19
+ // FromContext returns the ActivityPub client from the given context.
20
+ func FromContext (ctx context.Context ) (* Client , bool ) {
21
+ c , ok := ctx .Value (ClientKey ).(* Client )
22
+ return c , ok
23
+ }
24
+
25
+ // WithClient returns a new context with the given ActivityPub client.
26
+ func WithClient (ctx context.Context , c * Client ) context.Context {
27
+ return context .WithValue (ctx , ClientKey , c )
28
+ }
29
+
18
30
// Client is an ActivityPub client which can be used to fetch remote
19
31
// ActivityPub resources.
20
32
type Client struct {
21
33
keyID string
22
34
privateKey crypto.PrivateKey
23
35
}
24
36
25
- // NewClient returns a new ActivityPub client.
26
- func NewClient (signAs * models.Account ) (* Client , error ) {
27
- privPem , _ := pem .Decode (signAs .PrivateKey )
28
- if privPem == nil || privPem .Type != "RSA PRIVATE KEY" {
29
- return nil , errors .New ("expected RSA PRIVATE KEY" )
30
- }
31
-
32
- var parsedKey interface {}
33
- var err error
34
- if parsedKey , err = x509 .ParsePKCS1PrivateKey (privPem .Bytes ); err != nil {
35
- if parsedKey , err = x509 .ParsePKCS8PrivateKey (privPem .Bytes ); err != nil { // note this returns type `interface{}`
36
- return nil , err
37
- }
38
- }
37
+ // Signer represents an object that can sign HTTP requests.
38
+ type Signer interface {
39
+ PublicKeyID () string
40
+ PrivKey () (* rsa.PrivateKey , error )
41
+ }
39
42
40
- privateKey , ok := parsedKey .(* rsa.PrivateKey )
41
- if ! ok {
42
- return nil , errors .New ("expected *rsa.PrivateKey" )
43
+ // NewClient returns a new ActivityPub client.
44
+ func NewClient (signAs Signer ) (* Client , error ) {
45
+ privateKey , err := signAs .PrivKey ()
46
+ if err != nil {
47
+ return nil , err
43
48
}
44
-
45
49
return & Client {
46
- keyID : signAs .Actor . PublicKeyID (),
50
+ keyID : signAs .PublicKeyID (),
47
51
privateKey : privateKey ,
48
52
}, nil
49
53
}
@@ -52,7 +56,12 @@ func NewClient(signAs *models.Account) (*Client, error) {
52
56
func (c * Client ) Fetch (ctx context.Context , uri string , obj interface {}) error {
53
57
return requests .URL (uri ).
54
58
Accept (`application/ld+json; profile="https://www.w3.org/ns/activitystreams"` ).
55
- Transport (c ).
59
+ Transport (requests .RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
60
+ if err := httpsig .Sign (req , c .keyID , c .privateKey , nil ); err != nil {
61
+ return nil , fmt .Errorf ("failed to sign request: %w" , err )
62
+ }
63
+ return http .DefaultTransport .RoundTrip (req )
64
+ })).
56
65
CheckContentType (
57
66
"application/ld+json" ,
58
67
"application/activity+json" ,
@@ -64,19 +73,22 @@ func (c *Client) Fetch(ctx context.Context, uri string, obj interface{}) error {
64
73
Fetch (ctx )
65
74
}
66
75
67
- func (c * Client ) RoundTrip (req * http.Request ) (* http.Response , error ) {
68
- if err := httpsig .Sign (req , c .keyID , c .privateKey , nil ); err != nil {
69
- return nil , fmt .Errorf ("failed to sign request: %w" , err )
70
- }
71
- return http .DefaultTransport .RoundTrip (req )
72
- }
73
-
74
76
// Post posts the given ActivityPub object to the given URL.
75
77
func (c * Client ) Post (ctx context.Context , url string , obj map [string ]any ) error {
78
+ body , err := json .Marshal (obj )
79
+ if err != nil {
80
+ return err
81
+ }
76
82
return requests .URL (url ).
83
+ BodyBytes (body ).
77
84
Header ("Content-Type" , `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` ).
78
- BodyJSON (obj ).
79
- Transport (c ).
80
- CheckStatus (http .StatusOK , http .StatusCreated ).
85
+ Transport (requests .RoundTripFunc (func (req * http.Request ) (* http.Response , error ) {
86
+ if err := httpsig .Sign (req , c .keyID , c .privateKey , body ); err != nil {
87
+ return nil , fmt .Errorf ("failed to sign request: %w" , err )
88
+ }
89
+ return http .DefaultTransport .RoundTrip (req )
90
+ })).
91
+ ToWriter (os .Stderr ).
92
+ CheckStatus (http .StatusOK , http .StatusCreated , http .StatusAccepted ).
81
93
Fetch (ctx )
82
94
}
0 commit comments