Repository: jomei/notionapi
Pull request #145 (2023-07-05 22:14:52)

Fix 'token request' authorization header
This endpoint must use the Basic prefix, not the Bearer prefix.

[Notion docs](https://developers.notion.com/reference/create-a-token)

[Issue ref](https://github.com/jomei/notionapi/issues/142)

jomei: Great job! 
Thanks a lot 🔥 

Patch:
diff --git a/authentication.go b/authentication.go
index 0de77f0..6dd170f 100644
--- a/authentication.go
+++ b/authentication.go
@@ -20,7 +20,7 @@ type AuthenticationClient struct {
 //
 // See https://developers.notion.com/reference/create-a-token
 func (cc *AuthenticationClient) CreateToken(ctx context.Context, request *TokenCreateRequest) (*TokenCreateResponse, error) {
-	res, err := cc.apiClient.request(ctx, http.MethodPost, "oauth/token", nil, request)
+	res, err := cc.apiClient.requestImpl(ctx, http.MethodPost, "oauth/token", nil, request, true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/client.go b/client.go
index 69455ab..8ad7ed1 100644
--- a/client.go
+++ b/client.go
@@ -3,6 +3,7 @@ package notionapi
 import (
 	"bytes"
 	"context"
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -39,6 +40,10 @@ type Client struct {
 
 	Token Token
 
+	// used in Authorization header only for requests that require Basic authentication.
+	oauthID     string
+	oauthSecret string
+
 	Database       DatabaseService
 	Block          BlockService
 	Page           PageService
@@ -98,7 +103,19 @@ func WithRetry(retries int) ClientOption {
 	}
 }
 
+// WithOAuthAppCredentials sets the OAuth app ID and secret to use when fetching a token from Notion.
+func WithOAuthAppCredentials(id, secret string) ClientOption {
+	return func(c *Client) {
+		c.oauthID = id
+		c.oauthSecret = secret
+	}
+}
+
 func (c *Client) request(ctx context.Context, method string, urlStr string, queryParams map[string]string, requestBody interface{}) (*http.Response, error) {
+	return c.requestImpl(ctx, method, urlStr, queryParams, requestBody, false)
+}
+
+func (c *Client) requestImpl(ctx context.Context, method string, urlStr string, queryParams map[string]string, requestBody interface{}, basicAuth bool) (*http.Response, error) {
 	u, err := c.baseUrl.Parse(fmt.Sprintf("%s/%s", c.apiVersion, urlStr))
 	if err != nil {
 		return nil, err
@@ -125,7 +142,12 @@ func (c *Client) request(ctx context.Context, method string, urlStr string, quer
 		return nil, err
 	}
 
-	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token.String()))
+	if basicAuth {
+		cred := base64.StdEncoding.EncodeToString([]byte(c.oauthID + ":" + c.oauthSecret))
+		req.Header.Add("Authorization", fmt.Sprintf("Basic %s", cred))
+	} else {
+		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.Token.String()))
+	}
 	req.Header.Add("Notion-Version", c.notionVersion)
 	req.Header.Add("Content-Type", "application/json")
 


Test patch:
diff --git a/client_test.go b/client_test.go
index c71372b..f23a6d6 100644
--- a/client_test.go
+++ b/client_test.go
@@ -3,6 +3,8 @@ package notionapi_test
 import (
 	"context"
 	"net/http"
+	"net/http/httptest"
+	"net/url"
 	"os"
 	"testing"
 
@@ -80,3 +82,36 @@ func TestRateLimit(t *testing.T) {
 		}
 	})
 }
+
+func TestBasicAuthHeader(t *testing.T) {
+	t.Parallel()
+
+	srv := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+		auth := request.Header.Get("Authorization")
+		if auth != "Basic bXkgaWQgaGVyZTpzZWNyZXQgc2hoaA==" {
+			t.Errorf("expected basic auth, got %q", auth)
+		}
+		writer.WriteHeader(http.StatusNoContent)
+	}))
+	defer srv.Close()
+	srvURL, err := url.Parse(srv.URL)
+	if err != nil {
+		t.Fatalf("could not parse test server url")
+	}
+
+	c := newTestClient(func(req *http.Request) *http.Response {
+		req.URL = srvURL
+		resp, err := http.DefaultTransport.RoundTrip(req)
+		if err != nil {
+			t.Errorf("failed to make http request: %s", err.Error())
+		}
+		return resp
+	})
+
+	opts := []notionapi.ClientOption{
+		notionapi.WithHTTPClient(c),
+		notionapi.WithOAuthAppCredentials("my id here", "secret shhh"),
+	}
+	client := notionapi.NewClient("some_token", opts...)
+	_, _ = client.Authentication.CreateToken(context.Background(), &notionapi.TokenCreateRequest{})
+}