Skip to content

Commit

Permalink
Improve Admin API and Consumer API SDKs (#633)
Browse files Browse the repository at this point in the history
* refactor: specify api version in each route

* feat: factory methods for consumer api Client

* refactor: improve code quality

* refactor: improve code quality

* feat: add factory methods for Admin API SDK

* refactor: introduce ConsumerApiEndpoint

* refactor: use apiVersion on each endpoint instead of in http client

* refactor: get rid of Configuration

* chore: make method public

* chore: fix namespace

* chore: remove redundant parantheses

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
tnotheis and mergify[bot] authored May 3, 2024
1 parent f470de8 commit 60777fa
Show file tree
Hide file tree
Showing 38 changed files with 476 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Backbone.BuildingBlocks.SDK.Endpoints.Common;

namespace Backbone.AdminApi.Sdk.Endpoints.Common;
namespace Backbone.AdminApi.Sdk.Authentication;

public class XsrfAndApiKeyAuthenticator : IAuthenticator
{
Expand Down Expand Up @@ -40,7 +40,7 @@ private async Task<string> GetCookie()
[MemberNotNull(nameof(_xsrfToken), nameof(_xsrfCookie))]
private async Task RefreshToken()
{
HttpRequestMessage request = new(HttpMethod.Get, "xsrf");
HttpRequestMessage request = new(HttpMethod.Get, "api/v1/xsrf");
request.Headers.Add("X-API-KEY", _apiKey);

#pragma warning disable CS8774 // This warning ("Member must have a non-null value when exiting") must currently be disabled. (see https://github.com/dotnet/csharplang/discussions/ for details)
Expand Down
27 changes: 21 additions & 6 deletions AdminApi.Sdk/Client.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
using Backbone.AdminApi.Sdk.Endpoints.ApiKeyValidation;
using System.Text.Json;
using Backbone.AdminApi.Sdk.Authentication;
using Backbone.AdminApi.Sdk.Endpoints.ApiKeyValidation;
using Backbone.AdminApi.Sdk.Endpoints.Clients;
using Backbone.AdminApi.Sdk.Endpoints.Common;
using Backbone.AdminApi.Sdk.Endpoints.Identities;
using Backbone.AdminApi.Sdk.Endpoints.Logs;
using Backbone.AdminApi.Sdk.Endpoints.Metrics;
using Backbone.AdminApi.Sdk.Endpoints.Relationships;
using Backbone.AdminApi.Sdk.Endpoints.Tiers;
using Backbone.BuildingBlocks.SDK.Endpoints.Common;
using Backbone.Tooling.JsonConverters;

namespace Backbone.AdminApi.Sdk;

public class Client
{
public Client(Configuration config)
private Client(HttpClient httpClient, string apiKey)
{
var httpClient = new HttpClient { BaseAddress = new Uri(config.BaseUrl) };
var authenticator = new XsrfAndApiKeyAuthenticator(config.ApiKey, httpClient);
var endpointClient = new EndpointClient(httpClient, authenticator, config.JsonSerializerOptions);
var authenticator = new XsrfAndApiKeyAuthenticator(apiKey, httpClient);

var jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
jsonSerializerOptions.Converters.Add(new UrlSafeBase64ToByteArrayJsonConverter());

var endpointClient = new EndpointClient(httpClient, authenticator, jsonSerializerOptions);

ApiKeyValidation = new ApiKeyValidationEndpoint(endpointClient);
Clients = new ClientsEndpoint(endpointClient);
Expand All @@ -34,4 +39,14 @@ public Client(Configuration config)
public MetricsEndpoint Metrics { get; }
public RelationshipsEndpoint Relationships { get; }
public TiersEndpoint Tiers { get; }

public static Client Create(string baseUrl, string apiKey)
{
return Create(new HttpClient { BaseAddress = new Uri(baseUrl) }, apiKey);
}

public static Client Create(HttpClient httpClient, string apiKey)
{
return new Client(httpClient, apiKey);
}
}
16 changes: 0 additions & 16 deletions AdminApi.Sdk/Configuration.cs

This file was deleted.

12 changes: 12 additions & 0 deletions AdminApi.Sdk/Endpoints/AdminApiEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Backbone.BuildingBlocks.SDK.Endpoints.Common;

namespace Backbone.AdminApi.Sdk.Endpoints;

public abstract class AdminApiEndpoint : Endpoint
{
protected const string API_VERSION = "v1";

protected AdminApiEndpoint(EndpointClient client) : base(client)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace Backbone.AdminApi.Sdk.Endpoints.ApiKeyValidation;

public class ApiKeyValidationEndpoint(EndpointClient client) : Endpoint(client)
public class ApiKeyValidationEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<ValidateApiKeyResponse>> ValidateApiKeyUnauthenticated(ValidateApiKeyRequest? request)
=> await _client.PostUnauthenticated<ValidateApiKeyResponse>("ValidateApiKey", request);
=> await _client.PostUnauthenticated<ValidateApiKeyResponse>($"api/{API_VERSION}/ValidateApiKey", request);
}
14 changes: 7 additions & 7 deletions AdminApi.Sdk/Endpoints/Clients/ClientsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Clients;

public class ClientsEndpoint(EndpointClient client) : Endpoint(client)
public class ClientsEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<ListClientsResponse>> GetAllClients() => await _client.Get<ListClientsResponse>("Clients");
public async Task<ApiResponse<ListClientsResponse>> GetAllClients() => await _client.Get<ListClientsResponse>($"api/{API_VERSION}/Clients");

public async Task<ApiResponse<ClientInfo>> GetClient(string id) => await _client.Get<ClientInfo>($"Clients/{id}");
public async Task<ApiResponse<ClientInfo>> GetClient(string id) => await _client.Get<ClientInfo>($"api/{API_VERSION}/Clients/{id}");

public async Task<ApiResponse<CreateClientResponse>> CreateClient(CreateClientRequest request)
=> await _client.Post<CreateClientResponse>("Clients", request);
=> await _client.Post<CreateClientResponse>($"api/{API_VERSION}/Clients", request);

public async Task<ApiResponse<ClientInfo>> ChangeClientSecret(string id, ChangeClientSecretRequest request)
=> await _client.Patch<ClientInfo>($"Clients/{id}/ChangeSecret", request);
=> await _client.Patch<ClientInfo>($"api/{API_VERSION}/Clients/{id}/ChangeSecret", request);

public async Task<ApiResponse<ClientInfo>> UpdateClient(string id, UpdateClientRequest request)
=> await _client.Put<ClientInfo>($"Clients/{id}", request);
=> await _client.Put<ClientInfo>($"api/{API_VERSION}/Clients/{id}", request);

public async Task<ApiResponse<EmptyResponse>> DeleteClient(string id) => await _client.Delete<EmptyResponse>($"Clients/{id}");
public async Task<ApiResponse<EmptyResponse>> DeleteClient(string id) => await _client.Delete<EmptyResponse>($"api/{API_VERSION}/Clients/{id}");
}
14 changes: 7 additions & 7 deletions AdminApi.Sdk/Endpoints/Identities/IdentitiesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Identities;

public class IdentitiesEndpoint(EndpointClient client) : Endpoint(client)
public class IdentitiesEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<IndividualQuota>> CreateIndividualQuota(string address, CreateQuotaForIdentityRequest request)
=> await _client.Post<IndividualQuota>($"Identities/{address}/Quotas", request);
=> await _client.Post<IndividualQuota>($"api/{API_VERSION}/Identities/{address}/Quotas", request);

public async Task<ApiResponse<EmptyResponse>> DeleteIndividualQuota(string address, string quotaId)
=> await _client.Delete<EmptyResponse>($"Identities/{address}/Quotas/{quotaId}");
=> await _client.Delete<EmptyResponse>($"api/{API_VERSION}/Identities/{address}/Quotas/{quotaId}");

public async Task<ApiResponse<GetIdentityResponse>> GetIdentity(string address) => await _client.Get<GetIdentityResponse>($"Identities/{address}");
public async Task<ApiResponse<GetIdentityResponse>> GetIdentity(string address) => await _client.Get<GetIdentityResponse>($"api/{API_VERSION}/Identities/{address}");

public async Task<ApiResponse<EmptyResponse>> UpdateIdentityTier(string address, UpdateIdentityTierRequest request)
=> await _client.Put<EmptyResponse>($"Identities/{address}", request);
=> await _client.Put<EmptyResponse>($"api/{API_VERSION}/Identities/{address}", request);

public async Task<ApiResponse<CreateIdentityResponse>> CreateIdentity(CreateIdentityRequest request)
=> await _client.Post<CreateIdentityResponse>("Identities", request);
=> await _client.Post<CreateIdentityResponse>($"api/{API_VERSION}", request);

public async Task<ApiResponse<StartDeletionProcessAsSupportResponse>> StartDeletionProcess(string address)
=> await _client.Post<StartDeletionProcessAsSupportResponse>($"Identities/{address}/DeletionProcesses");
=> await _client.Post<StartDeletionProcessAsSupportResponse>($"api/{API_VERSION}/Identities/{address}/DeletionProcesses");
}
4 changes: 2 additions & 2 deletions AdminApi.Sdk/Endpoints/Logs/LogsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Logs;

public class LogsEndpoint(EndpointClient client) : Endpoint(client)
public class LogsEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<EmptyResponse>> CreateLog(LogRequest request) => await _client.Post<EmptyResponse>("Logs", request);
public async Task<ApiResponse<EmptyResponse>> CreateLog(LogRequest request) => await _client.Post<EmptyResponse>($"api/{API_VERSION}/Logs", request);
}
4 changes: 2 additions & 2 deletions AdminApi.Sdk/Endpoints/Metrics/MetricsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Metrics;

public class MetricsEndpoint(EndpointClient client) : Endpoint(client)
public class MetricsEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<ListMetricsResponse>> GetAllMetrics() => await _client.Get<ListMetricsResponse>("Metrics");
public async Task<ApiResponse<ListMetricsResponse>> GetAllMetrics() => await _client.Get<ListMetricsResponse>($"api/{API_VERSION}/Metrics");
}
6 changes: 3 additions & 3 deletions AdminApi.Sdk/Endpoints/Relationships/RelationshipsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Relationships;

public class RelationshipsEndpoint(EndpointClient client) : Endpoint(client)
public class RelationshipsEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<ListRelationshipsResponse>> GetAllRelationships(PaginationFilter? pagination = null)
=> await _client.Get<ListRelationshipsResponse>("Relationships", null, pagination);
=> await _client.Get<ListRelationshipsResponse>($"api/{API_VERSION}/Relationships", null, pagination);

public async Task<ApiResponse<ListRelationshipsResponse>> GetAllRelationships(string participant, PaginationFilter? pagination = null) => await _client
.Request<ListRelationshipsResponse>(HttpMethod.Get, "Relationships")
.Request<ListRelationshipsResponse>(HttpMethod.Get, $"api/{API_VERSION}/Relationships")
.Authenticate()
.WithPagination(pagination)
.AddQueryParameter("participant", participant)
Expand Down
14 changes: 7 additions & 7 deletions AdminApi.Sdk/Endpoints/Tiers/TiersEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@

namespace Backbone.AdminApi.Sdk.Endpoints.Tiers;

public class TiersEndpoint(EndpointClient client) : Endpoint(client)
public class TiersEndpoint(EndpointClient client) : AdminApiEndpoint(client)
{
public async Task<ApiResponse<ListTiersResponse>> ListTiers() => await _client.Get<ListTiersResponse>("Tiers");
public async Task<ApiResponse<ListTiersResponse>> ListTiers() => await _client.Get<ListTiersResponse>($"api/{API_VERSION}/Tiers");

public async Task<ApiResponse<TierDetails>> GetTier(string id) => await _client.Get<TierDetails>($"Tiers/{id}");
public async Task<ApiResponse<TierDetails>> GetTier(string id) => await _client.Get<TierDetails>($"api/{API_VERSION}/Tiers/{id}");

public async Task<ApiResponse<Tier>> CreateTier(CreateTierRequest request) => await _client.Post<Tier>("Tiers", request);
public async Task<ApiResponse<Tier>> CreateTier(CreateTierRequest request) => await _client.Post<Tier>($"api/{API_VERSION}/Tiers", request);

public async Task<ApiResponse<EmptyResponse>> DeleteTier(string id) => await _client.Delete<EmptyResponse>($"Tiers/{id}");
public async Task<ApiResponse<EmptyResponse>> DeleteTier(string id) => await _client.Delete<EmptyResponse>($"api/{API_VERSION}/Tiers/{id}");

public async Task<ApiResponse<TierQuotaDefinition>> AddTierQuota(string id, CreateQuotaForTierRequest request)
=> await _client.Post<TierQuotaDefinition>($"Tiers/{id}/Quotas", request);
=> await _client.Post<TierQuotaDefinition>($"api/{API_VERSION}/Tiers/{id}/Quotas", request);

public async Task<ApiResponse<EmptyResponse>> DeleteTierQuota(string id, string quotaId)
=> await _client.Delete<EmptyResponse>($"Tiers/{id}/Quotas/{quotaId}");
=> await _client.Delete<EmptyResponse>($"api/{API_VERSION}/Tiers/{id}/Quotas/{quotaId}");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ReSharper disable InconsistentNaming

#pragma warning disable IDE1006

namespace Backbone.BuildingBlocks.SDK.Crypto;

public enum CryptoExchangeAlgorithm
{
ECDH_P256 = 1,
ECDH_P521 = 2,
ECDH_X25519 = 3
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global

namespace Backbone.BuildingBlocks.SDK.Crypto;

public enum CryptoHashAlgorithm
{
SHA256 = 1,
SHA512 = 2,
BLAKE2B = 3
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// ReSharper disable InconsistentNaming

#pragma warning disable IDE1006

namespace Backbone.BuildingBlocks.SDK.Crypto;

public class CryptoSignaturePublicKey
{
public required CryptoExchangeAlgorithm alg;
public required string pub;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// ReSharper disable InconsistentNaming

#pragma warning disable IDE1006

namespace Backbone.BuildingBlocks.SDK.Crypto;

public class CryptoSignatureSignedChallenge
{
public required CryptoHashAlgorithm alg;
public required byte[] sig;
}
11 changes: 0 additions & 11 deletions BuildingBlocks/src/BuildingBlocks.SDK/UriUtils.cs

This file was deleted.

11 changes: 11 additions & 0 deletions ConsumerApi.Sdk/Authentication/AnonymousAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Backbone.BuildingBlocks.SDK.Endpoints.Common;

namespace Backbone.ConsumerApi.Sdk.Authentication;

public class AnonymousAuthenticator : IAuthenticator
{
public Task Authenticate(HttpRequestMessage request)
{
throw new Exception("In order to use an authenticated request, you have to provide an user credentials.");
}
}
13 changes: 13 additions & 0 deletions ConsumerApi.Sdk/Authentication/ClientCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Backbone.ConsumerApi.Sdk.Authentication;

public class ClientCredentials
{
public ClientCredentials(string clientId, string clientSecret)
{
ClientId = clientId;
ClientSecret = clientSecret;
}

public string ClientId { get; }
public string ClientSecret { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,37 @@
using System.Text.Json.Serialization;
using Backbone.BuildingBlocks.SDK.Endpoints.Common;

namespace Backbone.ConsumerApi.Sdk.Endpoints.Common;
namespace Backbone.ConsumerApi.Sdk.Authentication;

public class OAuthAuthenticator : IAuthenticator
{
private static readonly JsonSerializerOptions SERIALIZER_OPTIONS = new() { PropertyNameCaseInsensitive = true };

private readonly Configuration.AuthenticationConfiguration _config;
private readonly HttpClient _httpClient;
private readonly Dictionary<string, string> _jwtRequestData;
private Jwt? _jwt;

public OAuthAuthenticator(Configuration.AuthenticationConfiguration config, HttpClient httpClient)
{
_config = config;
_httpClient = httpClient;

_jwtRequestData = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", config.Username },
{ "password", config.Password },
{ "client_id", config.ClientId },
{ "client_secret", config.ClientSecret }
{ "username", config.UserCredentials?.Username! },
{ "password", config.UserCredentials?.Password! },
{ "client_id", config.ClientCredentials.ClientId },
{ "client_secret", config.ClientCredentials.ClientSecret }
};
}

public async Task Authenticate(HttpRequestMessage request)
{
if (_config.UserCredentials == null)
throw new InvalidOperationException("User credentials are required in order to authenticate this request.");

request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await GetJwt());
}

Expand Down
13 changes: 13 additions & 0 deletions ConsumerApi.Sdk/Authentication/UserCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Backbone.ConsumerApi.Sdk.Authentication;

public class UserCredentials
{
public UserCredentials(string username, string password)
{
Username = username;
Password = password;
}

public string Username { get; }
public string Password { get; }
}
Loading

0 comments on commit 60777fa

Please sign in to comment.