Skip to content

Commit 1a55497

Browse files
Merge pull request #473 from notion-dotnet/fix/472-add-a-way-to-use-basic-authentication-for-authentication-endpoint
Fix: add a support to pass basic authentication for authentication endpoint
2 parents 49fea85 + 442342c commit 1a55497

File tree

10 files changed

+117
-14
lines changed

10 files changed

+117
-14
lines changed

Src/Notion.Client/Api/Authentication/CreateToken/AuthenticationClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ public async Task<CreateTokenResponse> CreateTokenAsync(
99
CreateTokenRequest createTokenRequest,
1010
CancellationToken cancellationToken = default)
1111
{
12-
var body = (ICreateTokenBodyParameters)createTokenRequest;
12+
ICreateTokenBodyParameters body = createTokenRequest;
13+
IBasicAuthenticationParameters basicAuth = createTokenRequest;
1314

1415
return await _client.PostAsync<CreateTokenResponse>(
1516
ApiEndpoints.AuthenticationUrls.CreateToken(),
1617
body,
18+
basicAuthenticationParameters: basicAuth,
1719
cancellationToken: cancellationToken
1820
);
1921
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Notion.Client
22
{
3-
public class CreateTokenRequest : ICreateTokenBodyParameters
3+
public class CreateTokenRequest : ICreateTokenBodyParameters, IBasicAuthenticationParameters
44
{
55
public string GrantType => "authorization_code";
66

@@ -9,5 +9,9 @@ public class CreateTokenRequest : ICreateTokenBodyParameters
99
public string RedirectUri { get; set; }
1010

1111
public ExternalAccount ExternalAccount { get; set; }
12+
13+
public string ClientId { get; set; }
14+
15+
public string ClientSecret { get; set; }
1216
}
1317
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace Notion.Client
5+
{
6+
internal static class HeaderHelpers
7+
{
8+
public static string GetBasicAuthHeaderValue(IBasicAuthenticationParameters basicAuth)
9+
{
10+
if (basicAuth == null)
11+
{
12+
return null;
13+
}
14+
15+
var basicAuthString = $"{basicAuth.ClientId}:{basicAuth.ClientSecret}";
16+
var basicAuthHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(basicAuthString));
17+
18+
return basicAuthHeaderValue;
19+
}
20+
}
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Notion.Client
2+
{
3+
public interface IBasicAuthenticationParameters
4+
{
5+
string ClientId { get; }
6+
string ClientSecret { get; }
7+
}
8+
}

Src/Notion.Client/Api/Authentication/RevokeToken/AuthenticationClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ public async Task RevokeTokenAsync(
99
RevokeTokenRequest revokeTokenRequest,
1010
CancellationToken cancellationToken = default)
1111
{
12-
var body = (IRevokeTokenBodyParameters)revokeTokenRequest;
12+
IRevokeTokenBodyParameters body = revokeTokenRequest;
13+
IBasicAuthenticationParameters basicAuth = revokeTokenRequest;
1314

1415
await _client.PostAsync<RevokeTokenResponse>(
1516
ApiEndpoints.AuthenticationUrls.RevokeToken(),
1617
body,
18+
basicAuthenticationParameters: basicAuth,
1719
cancellationToken: cancellationToken
1820
);
1921
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
namespace Notion.Client
22
{
3-
public class RevokeTokenRequest : IRevokeTokenBodyParameters
3+
public class RevokeTokenRequest : IRevokeTokenBodyParameters, IBasicAuthenticationParameters
44
{
55
public string Token { get; set; }
6+
7+
public string ClientId { get; set; }
8+
9+
public string ClientSecret { get; set; }
610
}
711
}

Src/Notion.Client/RestClient/IRestClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Task<T> PostAsync<T>(
2020
IEnumerable<KeyValuePair<string, string>> queryParams = null,
2121
IDictionary<string, string> headers = null,
2222
JsonSerializerSettings serializerSettings = null,
23+
IBasicAuthenticationParameters basicAuthenticationParameters = null,
2324
CancellationToken cancellationToken = default);
2425

2526
Task<T> PatchAsync<T>(

Src/Notion.Client/RestClient/RestClient.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public async Task<T> PostAsync<T>(
4848
IEnumerable<KeyValuePair<string, string>> queryParams = null,
4949
IDictionary<string, string> headers = null,
5050
JsonSerializerSettings serializerSettings = null,
51+
IBasicAuthenticationParameters basicAuthenticationParameters = null,
5152
CancellationToken cancellationToken = default)
5253
{
5354
void AttachContent(HttpRequestMessage httpRequest)
@@ -56,8 +57,15 @@ void AttachContent(HttpRequestMessage httpRequest)
5657
Encoding.UTF8, "application/json");
5758
}
5859

59-
var response = await SendAsync(uri, HttpMethod.Post, queryParams, headers, AttachContent,
60-
cancellationToken);
60+
var response = await SendAsync(
61+
uri,
62+
HttpMethod.Post,
63+
queryParams,
64+
headers,
65+
AttachContent,
66+
basicAuthenticationParameters,
67+
cancellationToken
68+
);
6169

6270
return await response.ParseStreamAsync<T>(serializerSettings);
6371
}
@@ -77,7 +85,7 @@ void AttachContent(HttpRequestMessage httpRequest)
7785
}
7886

7987
var response = await SendAsync(uri, new HttpMethod("PATCH"), queryParams, headers, AttachContent,
80-
cancellationToken);
88+
basicAuthenticationParameters: null, cancellationToken);
8189

8290
return await response.ParseStreamAsync<T>(serializerSettings);
8391
}
@@ -88,7 +96,8 @@ public async Task DeleteAsync(
8896
IDictionary<string, string> headers = null,
8997
CancellationToken cancellationToken = default)
9098
{
91-
await SendAsync(uri, HttpMethod.Delete, queryParams, headers, null, cancellationToken);
99+
await SendAsync(uri, HttpMethod.Delete, queryParams, headers, null,
100+
basicAuthenticationParameters: null, cancellationToken);
92101
}
93102

94103
private static ClientOptions MergeOptions(ClientOptions options)
@@ -116,6 +125,7 @@ private static async Task<Exception> BuildException(HttpResponseMessage response
116125
if (errorResponse.ErrorCode == NotionAPIErrorCode.RateLimited)
117126
{
118127
var retryAfter = response.Headers.RetryAfter.Delta;
128+
119129
return new NotionApiRateLimitException(
120130
response.StatusCode,
121131
errorResponse.ErrorCode,
@@ -139,14 +149,16 @@ private async Task<HttpResponseMessage> SendAsync(
139149
IEnumerable<KeyValuePair<string, string>> queryParams = null,
140150
IDictionary<string, string> headers = null,
141151
Action<HttpRequestMessage> attachContent = null,
152+
IBasicAuthenticationParameters basicAuthenticationParameters = null,
142153
CancellationToken cancellationToken = default)
143154
{
144155
EnsureHttpClient();
145156

146157
requestUri = AddQueryString(requestUri, queryParams);
147158

148159
using var httpRequest = new HttpRequestMessage(httpMethod, requestUri);
149-
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.AuthToken);
160+
161+
httpRequest.Headers.Authorization = CreateAuthenticationHeader(basicAuthenticationParameters);
150162
httpRequest.Headers.Add("Notion-Version", _options.NotionVersion);
151163

152164
if (headers != null)
@@ -166,6 +178,13 @@ private async Task<HttpResponseMessage> SendAsync(
166178
return response;
167179
}
168180

181+
private AuthenticationHeaderValue CreateAuthenticationHeader(IBasicAuthenticationParameters basicAuth)
182+
{
183+
return basicAuth != null
184+
? new AuthenticationHeaderValue("Basic", HeaderHelpers.GetBasicAuthHeaderValue(basicAuth))
185+
: new AuthenticationHeaderValue("Bearer", _options.AuthToken);
186+
}
187+
169188
private static void AddHeaders(HttpRequestMessage request, IDictionary<string, string> headers)
170189
{
171190
foreach (var header in headers)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Threading.Tasks;
2+
using Notion.Client;
3+
using Xunit;
4+
5+
namespace Notion.IntegrationTests;
6+
7+
public class AuthenticationClientTests : IntegrationTestBase
8+
{
9+
private readonly string _clientId = GetEnvironmentVariableRequired("NOTION_CLIENT_ID");
10+
private readonly string _clientSecret = GetEnvironmentVariableRequired("NOTION_CLIENT_SECRET");
11+
12+
[Fact]
13+
public async Task Create_and_revoke_token()
14+
{
15+
// Arrange
16+
var createRequest = new CreateTokenRequest
17+
{
18+
Code = "03b3bd2d-6b96-4104-a9f4-ee04d881532c",
19+
ClientId = _clientId,
20+
ClientSecret = _clientSecret,
21+
RedirectUri = "https://localhost:5001",
22+
};
23+
24+
// Act
25+
var response = await Client.AuthenticationClient.CreateTokenAsync(createRequest);
26+
27+
// Assert
28+
Assert.NotNull(response);
29+
Assert.NotNull(response.AccessToken);
30+
31+
// revoke token
32+
await Client.AuthenticationClient.RevokeTokenAsync(new RevokeTokenRequest
33+
{
34+
Token = response.AccessToken,
35+
ClientId = _clientId,
36+
ClientSecret = _clientSecret
37+
});
38+
}
39+
}

Test/Notion.IntegrationTests/IntegrationTestBase.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ protected IntegrationTestBase()
1515

1616
Client = NotionClientFactory.Create(options);
1717

18-
ParentPageId = Environment.GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
19-
?? throw new InvalidOperationException("Parent page id is required.");
20-
21-
ParentDatabaseId = Environment.GetEnvironmentVariable("NOTION_PARENT_DATABASE_ID")
22-
?? throw new InvalidOperationException("Parent database id is required.");
18+
ParentPageId = GetEnvironmentVariableRequired("NOTION_PARENT_PAGE_ID");
19+
ParentDatabaseId = GetEnvironmentVariableRequired("NOTION_PARENT_DATABASE_ID");
20+
}
21+
22+
protected static string GetEnvironmentVariableRequired(string envName)
23+
{
24+
return Environment.GetEnvironmentVariable(envName) ??
25+
throw new InvalidOperationException($"Environment variable '{envName}' is required.");
2326
}
2427
}

0 commit comments

Comments
 (0)