Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ public async Task<CreateTokenResponse> CreateTokenAsync(
CreateTokenRequest createTokenRequest,
CancellationToken cancellationToken = default)
{
var body = (ICreateTokenBodyParameters)createTokenRequest;
ICreateTokenBodyParameters body = createTokenRequest;
IBasicAuthenticationParameters basicAuth = createTokenRequest;

return await _client.PostAsync<CreateTokenResponse>(
ApiEndpoints.AuthenticationUrls.CreateToken(),
body,
basicAuthenticationParameters: basicAuth,
cancellationToken: cancellationToken
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Notion.Client
{
public class CreateTokenRequest : ICreateTokenBodyParameters
public class CreateTokenRequest : ICreateTokenBodyParameters, IBasicAuthenticationParameters
{
public string GrantType => "authorization_code";

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

public ExternalAccount ExternalAccount { get; set; }

public string ClientId { get; set; }

public string ClientSecret { get; set; }
}
}
21 changes: 21 additions & 0 deletions Src/Notion.Client/Api/Authentication/HeaderHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Text;

namespace Notion.Client
{
internal static class HeaderHelpers
{
public static string GetBasicAuthHeaderValue(IBasicAuthenticationParameters basicAuth)
{
if (basicAuth == null)
{
return null;
}

var basicAuthString = $"{basicAuth.ClientId}:{basicAuth.ClientSecret}";
var basicAuthHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(basicAuthString));

return basicAuthHeaderValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Notion.Client
{
public interface IBasicAuthenticationParameters
{
string ClientId { get; }
string ClientSecret { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ public async Task RevokeTokenAsync(
RevokeTokenRequest revokeTokenRequest,
CancellationToken cancellationToken = default)
{
var body = (IRevokeTokenBodyParameters)revokeTokenRequest;
IRevokeTokenBodyParameters body = revokeTokenRequest;
IBasicAuthenticationParameters basicAuth = revokeTokenRequest;

await _client.PostAsync<RevokeTokenResponse>(
ApiEndpoints.AuthenticationUrls.RevokeToken(),
body,
basicAuthenticationParameters: basicAuth,
cancellationToken: cancellationToken
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
namespace Notion.Client
{
public class RevokeTokenRequest : IRevokeTokenBodyParameters
public class RevokeTokenRequest : IRevokeTokenBodyParameters, IBasicAuthenticationParameters
{
public string Token { get; set; }

public string ClientId { get; set; }

public string ClientSecret { get; set; }
}
}
1 change: 1 addition & 0 deletions Src/Notion.Client/RestClient/IRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Task<T> PostAsync<T>(
IEnumerable<KeyValuePair<string, string>> queryParams = null,
IDictionary<string, string> headers = null,
JsonSerializerSettings serializerSettings = null,
IBasicAuthenticationParameters basicAuthenticationParameters = null,
CancellationToken cancellationToken = default);

Task<T> PatchAsync<T>(
Expand Down
29 changes: 24 additions & 5 deletions Src/Notion.Client/RestClient/RestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public async Task<T> PostAsync<T>(
IEnumerable<KeyValuePair<string, string>> queryParams = null,
IDictionary<string, string> headers = null,
JsonSerializerSettings serializerSettings = null,
IBasicAuthenticationParameters basicAuthenticationParameters = null,
CancellationToken cancellationToken = default)
{
void AttachContent(HttpRequestMessage httpRequest)
Expand All @@ -56,8 +57,15 @@ void AttachContent(HttpRequestMessage httpRequest)
Encoding.UTF8, "application/json");
}

var response = await SendAsync(uri, HttpMethod.Post, queryParams, headers, AttachContent,
cancellationToken);
var response = await SendAsync(
uri,
HttpMethod.Post,
queryParams,
headers,
AttachContent,
basicAuthenticationParameters,
cancellationToken
);

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

var response = await SendAsync(uri, new HttpMethod("PATCH"), queryParams, headers, AttachContent,
cancellationToken);
basicAuthenticationParameters: null, cancellationToken);

return await response.ParseStreamAsync<T>(serializerSettings);
}
Expand All @@ -88,7 +96,8 @@ public async Task DeleteAsync(
IDictionary<string, string> headers = null,
CancellationToken cancellationToken = default)
{
await SendAsync(uri, HttpMethod.Delete, queryParams, headers, null, cancellationToken);
await SendAsync(uri, HttpMethod.Delete, queryParams, headers, null,
basicAuthenticationParameters: null, cancellationToken);
}

private static ClientOptions MergeOptions(ClientOptions options)
Expand Down Expand Up @@ -116,6 +125,7 @@ private static async Task<Exception> BuildException(HttpResponseMessage response
if (errorResponse.ErrorCode == NotionAPIErrorCode.RateLimited)
{
var retryAfter = response.Headers.RetryAfter.Delta;

return new NotionApiRateLimitException(
response.StatusCode,
errorResponse.ErrorCode,
Expand All @@ -139,14 +149,16 @@ private async Task<HttpResponseMessage> SendAsync(
IEnumerable<KeyValuePair<string, string>> queryParams = null,
IDictionary<string, string> headers = null,
Action<HttpRequestMessage> attachContent = null,
IBasicAuthenticationParameters basicAuthenticationParameters = null,
CancellationToken cancellationToken = default)
{
EnsureHttpClient();

requestUri = AddQueryString(requestUri, queryParams);

using var httpRequest = new HttpRequestMessage(httpMethod, requestUri);
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.AuthToken);

httpRequest.Headers.Authorization = CreateAuthenticationHeader(basicAuthenticationParameters);
httpRequest.Headers.Add("Notion-Version", _options.NotionVersion);

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

private AuthenticationHeaderValue CreateAuthenticationHeader(IBasicAuthenticationParameters basicAuth)
{
return basicAuth != null
? new AuthenticationHeaderValue("Basic", HeaderHelpers.GetBasicAuthHeaderValue(basicAuth))
: new AuthenticationHeaderValue("Bearer", _options.AuthToken);
}

private static void AddHeaders(HttpRequestMessage request, IDictionary<string, string> headers)
{
foreach (var header in headers)
Expand Down
39 changes: 39 additions & 0 deletions Test/Notion.IntegrationTests/AuthenticationClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Notion.Client;
using Xunit;

namespace Notion.IntegrationTests;

public class AuthenticationClientTests : IntegrationTestBase
{
private readonly string _clientId = GetEnvironmentVariableRequired("NOTION_CLIENT_ID");
private readonly string _clientSecret = GetEnvironmentVariableRequired("NOTION_CLIENT_SECRET");

[Fact]
public async Task Create_and_revoke_token()
{
// Arrange
var createRequest = new CreateTokenRequest
{
Code = "03b3bd2d-6b96-4104-a9f4-ee04d881532c",
ClientId = _clientId,
ClientSecret = _clientSecret,
RedirectUri = "https://localhost:5001",
};

// Act
var response = await Client.AuthenticationClient.CreateTokenAsync(createRequest);

// Assert
Assert.NotNull(response);
Assert.NotNull(response.AccessToken);

// revoke token
await Client.AuthenticationClient.RevokeTokenAsync(new RevokeTokenRequest
{
Token = response.AccessToken,
ClientId = _clientId,
ClientSecret = _clientSecret
});
}
}
13 changes: 8 additions & 5 deletions Test/Notion.IntegrationTests/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ protected IntegrationTestBase()

Client = NotionClientFactory.Create(options);

ParentPageId = Environment.GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
?? throw new InvalidOperationException("Parent page id is required.");

ParentDatabaseId = Environment.GetEnvironmentVariable("NOTION_PARENT_DATABASE_ID")
?? throw new InvalidOperationException("Parent database id is required.");
ParentPageId = GetEnvironmentVariableRequired("NOTION_PARENT_PAGE_ID");
ParentDatabaseId = GetEnvironmentVariableRequired("NOTION_PARENT_DATABASE_ID");
}

protected static string GetEnvironmentVariableRequired(string envName)
{
return Environment.GetEnvironmentVariable(envName) ??
throw new InvalidOperationException($"Environment variable '{envName}' is required.");
}
}
Loading