Skip to content

chore: Cleanup Access Layer #448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2025
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ Prefix your items with `(Template)` if the change is about the template and not
## 3.10.X
- Added Dependency Injection validation in the development environment.
- Cleaned up the persistence configuration files (removed unused parameters and updated documentation).
- Updated Contributing documentation.
- Adding a workaround for a bug with the language change on android.
- Cleanup the 'Access' layer's code (renamed repositories into API clients, removed unused namespaces, and sealed some classes).

## 3.9.X
- Removed unnecessary `IsExternalInit.cs` files.
Expand All @@ -21,7 +23,6 @@ Prefix your items with `(Template)` if the change is about the template and not
- Optimized the .NET workloads install process.
- Fixed the iOS application icon size.
- Added VM Disposal in Functional Tests.
- Updated Contributing documentation.

## 3.8.X
- Updated from .NET 8 to .NET 9.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ namespace ApplicationTemplate.DataAccess;

public sealed class AuthenticationApiClientMock : IAuthenticationApiClient
{
private const int TokenExpirationSeconds = 600;

private readonly JsonSerializerOptions _serializerOptions;
private readonly IOptionsMonitor<MockOptions> _mockOptionsMonitor;
private readonly TimeProvider _timeProvider;

public AuthenticationApiClientMock(JsonSerializerOptions serializerOptions, IOptionsMonitor<MockOptions> mockOptionsMonitor)
public AuthenticationApiClientMock(JsonSerializerOptions serializerOptions, IOptionsMonitor<MockOptions> mockOptionsMonitor, TimeProvider timeProvider)
{
_serializerOptions = serializerOptions;
_mockOptionsMonitor = mockOptionsMonitor;
_timeProvider = timeProvider;
}

public async Task<AuthenticationData> CreateAccount(CancellationToken ct, string email, string password)
{
await SimulateDelay(ct);

// We authenticate the user on account creation, since we don't have a backend to register and validate the user
return CreateAuthenticationData();
// We authenticate the user on account creation, since we don't have a backend to register and validate the user.
return CreateAuthenticationData(email: email);
}

public async Task ResetPassword(CancellationToken ct, string email)
Expand All @@ -36,7 +40,7 @@ public async Task<AuthenticationData> Login(CancellationToken ct, string email,
{
await SimulateDelay(ct);

return CreateAuthenticationData();
return CreateAuthenticationData(email: email);
}

public async Task<AuthenticationData> RefreshToken(CancellationToken ct, AuthenticationData unauthorizedToken)
Expand All @@ -45,41 +49,58 @@ public async Task<AuthenticationData> RefreshToken(CancellationToken ct, Authent

await SimulateDelay(ct);

return CreateAuthenticationData(unauthorizedToken.AccessTokenPayload);
}

private AuthenticationData CreateAuthenticationData(AuthenticationToken token = null, TimeSpan? timeToLive = null)
{
var encodedJwt = CreateJsonWebToken(token, timeToLive);
var jwt = new JwtData<AuthenticationToken>(encodedJwt, _serializerOptions);

return new AuthenticationData()
{
AccessToken = jwt.Token,
RefreshToken = Guid.NewGuid().ToString(format: null, CultureInfo.InvariantCulture),
Expiration = jwt.Payload.Expiration,
};
return CreateAuthenticationData(token: unauthorizedToken.AccessToken.Payload);
}

private string CreateJsonWebToken(AuthenticationToken token = null, TimeSpan? timeToLive = null)
/// <summary>
/// Creates a JSON Web Token.
/// </summary>
/// <remarks>
/// This function has been made public and static for testing purposes.
/// </remarks>
/// <param name="token">The token to use.</param>
/// <param name="email">The email or unique name to store in the token.</param>
/// <param name="now">The current date and time to use for the authentication token.</param>
/// <param name="serializerOptions">The serializer options to use for the token serialization.</param>
/// <returns>The JSON Web token.</returns>
public static string CreateJsonWebToken(AuthenticationToken token = null, string email = null, DateTimeOffset? now = null, JsonSerializerOptions serializerOptions = null)
{
const string header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; // alg=HS256, type=JWT
const string signature = "QWqnPP8W6ymexz74P6quP-oG-wxr7vMGqrEL8y_tV6M"; // dummy stuff

var now = DateTimeOffset.Now;
now ??= DateTimeOffset.Now;

token = token ?? new AuthenticationToken(default, DateTimeOffset.MinValue, DateTimeOffset.MinValue);
token ??= new AuthenticationToken()
{
Email = email,
Expiration = now.Value.AddSeconds(TokenExpirationSeconds),
IssuedAt = now.Value,
};

string payload;
using (var stream = new MemoryStream())
{
JsonSerializer.Serialize(stream, token, _serializerOptions);
var test = JsonSerializer.Serialize(token, serializerOptions);

JsonSerializer.Serialize(stream, token, serializerOptions);
payload = Convert.ToBase64String(stream.ToArray());
}

return header + '.' + payload + '.' + signature;
}

private AuthenticationData CreateAuthenticationData(AuthenticationToken token = null, string email = null)
{
var now = _timeProvider.GetLocalNow();
var encodedJwt = CreateJsonWebToken(token, email, now, _serializerOptions);

return new AuthenticationData()
{
AccessToken = new JwtData<AuthenticationToken>(encodedJwt, _serializerOptions),
RefreshToken = Guid.NewGuid().ToString(format: null, CultureInfo.InvariantCulture),
};
}

private async Task SimulateDelay(CancellationToken ct)
{
if (_mockOptionsMonitor.CurrentValue.IsDelayForSimulatedApiCallsEnabled)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Text.Json.Serialization;
using MallardMessageHandlers;

namespace ApplicationTemplate.DataAccess;

public sealed class AuthenticationData : IAuthenticationToken
{
[JsonPropertyName("access_token")]
[JsonConverter(typeof(JwtDataJsonConverter<AuthenticationToken>))]
public JwtData<AuthenticationToken> AccessToken { get; init; }

[JsonPropertyName("refresh_token")]
public string RefreshToken { get; init; }

[JsonIgnore]
string IAuthenticationToken.AccessToken => AccessToken?.Token;

[JsonIgnore]
public bool CanBeRefreshed => !string.IsNullOrEmpty(RefreshToken);

[JsonIgnore]
public string Email => AccessToken?.Payload?.Email;

[JsonIgnore]
public DateTimeOffset? Expiration => AccessToken?.Payload?.Expiration;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Text.Json.Serialization;

namespace ApplicationTemplate.DataAccess;

public sealed class AuthenticationToken
{
[JsonPropertyName("unique_name")]
public string Email { get; init; }

[JsonPropertyName("exp")]
[JsonConverter(typeof(UnixTimestampJsonConverter))]
public DateTimeOffset Expiration { get; init; }

[JsonPropertyName("iat")]
[JsonConverter(typeof(UnixTimestampJsonConverter))]
public DateTimeOffset IssuedAt { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;

namespace ApplicationTemplate.DataAccess;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace ApplicationTemplate.DataAccess;

public sealed class ErrorData
{
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;

namespace ApplicationTemplate.DataAccess;

public record PostData
public sealed record PostData
{
public PostData(long id, string title, string body, long userIdentifier)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ApplicationTemplate.DataAccess;

public sealed class PostErrorResponse
{
public PostData Data { get; }

public ErrorData Error { get; }
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Refit;

Expand All @@ -11,7 +8,7 @@ namespace ApplicationTemplate.DataAccess;
/// Provides access to the posts API.
/// </summary>
[Headers("Authorization: Bearer")]
public interface IPostsRepository
public interface IPostsApiClient
{
/// <summary>
/// Gets the list of all posts.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace ApplicationTemplate.DataAccess;

public sealed class PostsApiClientException : Exception
{
public PostsApiClientException()
{
}

public PostsApiClientException(string message)
: base(message)
{
}

public PostsApiClientException(string message, Exception innerException)
: base(message, innerException)
{
}

public PostsApiClientException(PostErrorResponse errorResponse)
{
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Refit;

namespace ApplicationTemplate.DataAccess;

public class PostsRepositoryMock : BaseMock, IPostsRepository
public sealed class PostsApiClientMock : BaseMock, IPostsApiClient
{
public PostsRepositoryMock(JsonSerializerOptions serializerOptions)
public PostsApiClientMock(JsonSerializerOptions serializerOptions)
: base(serializerOptions)
{
}
Expand Down
Loading