From 1fe1e04c3b413109bd3d7fddea975513c455b098 Mon Sep 17 00:00:00 2001 From: DJ O'Leary Date: Thu, 3 Sep 2026 02:35:45 +0200 Subject: feat(oauth): create oauth1 client --- src/oauth/client.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/oauth/client.go (limited to 'src/oauth/client.go') diff --git a/src/oauth/client.go b/src/oauth/client.go new file mode 100644 index 0000000..684cb20 --- /dev/null +++ b/src/oauth/client.go @@ -0,0 +1,112 @@ +// The oauth package provides a Client that authenticates requests +// automatically with OAuth1. +// +// [Cardmarket OAuth] and [RFC 5849] were used as reference. +// +// [Cardmarket OAuth]: https://apiv2.cardmarket.com/ws/documentation/API:Auth_OAuthHeader +// [RFC 5849]: https://datatracker.ietf.org/doc/html/rfc5849#section-3.1 +package oauth + +import ( + "context" + "fmt" + "io" + "log/slog" + "net/http" + "strings" +) + +const ( + headerAuthorization = "Authorization" + headerContentType = "Content-Type" +) + +type ConsumerToken struct { + Token string + Secret string +} + +type AccessToken struct { + Token string + Secret string +} + +type Tokens struct { + Consumer ConsumerToken + Access AccessToken +} + +type Client struct { + tokens Tokens +} + +func NewClient(t Tokens) *Client { + return &Client{tokens: t} +} + +func (c *Client) GetWithContext( + ctx context.Context, + url string, +) (*http.Response, error) { + return c.request(ctx, http.MethodGet, url, "", nil) +} + +func (c *Client) PostWithContext( + ctx context.Context, + url, contentType string, + body io.Reader, +) (*http.Response, error) { + return c.request(ctx, http.MethodPost, url, contentType, body) +} + +// request hydrates and fulfils a http.Request using information stored in the client +func (c *Client) request( + ctx context.Context, + method string, + url, contentType string, + body io.Reader, +) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + return nil, err + } + + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + + if err = c.authorize(req); err != nil { + return nil, err + } + + slog.DebugContext( + ctx, + "sending request", + "method", req.Method, + "url", req.URL.String(), + "contentType", req.Header.Get(headerContentType), + "authorization", strings.Join(req.Header.Values(headerAuthorization), ", "), + ) + + return http.DefaultClient.Do(req) +} + +func (c *Client) authorize(req *http.Request) error { + baseURL := fmt.Sprintf( + "%s://%s%s", + strings.ToLower(req.URL.Scheme), + strings.ToLower(req.URL.Host), + req.URL.Path, + ) + + ah := newAuthHeader( + req.Method, + baseURL, + nil, + c.tokens, + ) + + req.Header.Set(headerAuthorization, ah.String()) + + return nil +} -- cgit v1.2.3