Skip to content

Commit

Permalink
Rework config and DI
Browse files Browse the repository at this point in the history
  • Loading branch information
LucHeart committed May 8, 2024
1 parent 894f828 commit 39f1c78
Show file tree
Hide file tree
Showing 30 changed files with 267 additions and 144 deletions.
2 changes: 1 addition & 1 deletion API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>OpenShock.API</AssemblyName>
<RootNamespace>OpenShock.API</RootNamespace>
<AssemblyVersion>2.7.0</AssemblyVersion>
<AssemblyVersion>3.0.0</AssemblyVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Company>OpenShock</Company>
<Product>API</Product>
Expand Down
7 changes: 0 additions & 7 deletions API/APIGlobals.cs

This file was deleted.

40 changes: 13 additions & 27 deletions API/ApiConfig.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
using System.ComponentModel.DataAnnotations;
using OpenShock.API.Services.Email.Mailjet.Mail;
using OpenShock.ServicesCommon.Config;

namespace OpenShock.API;

public class ApiConfig
public sealed class ApiConfig : BaseConfig
{
[Required] public required Uri FrontendBaseUrl { get; init; }
[Required(AllowEmptyStrings = false)] public required string CookieDomain { get; init; }
[Required] public required DbConfig Db { get; init; }
[Required] public required RedisConfig Redis { get; init; }
[Required] public required FrontendConfig Frontend { get; init; }
[Required] public required MailConfig Mail { get; init; }
[Required] public required TurnstileConfig Turnstile { get; init; }

public sealed class TurnstileConfig
{
[Required] public required bool Enabled { get; init; }
public required string? SecretKey { get; init; }
public required string? SiteKey { get; init; }
public string? SecretKey { get; init; }
public string? SiteKey { get; init; }
}

public sealed class MailConfig
{
[Required] public required MailType Type { get; init; }
[Required] public required Contact Sender { get; init; }
public required MailjetConfig? Mailjet { get; init; }
public required SmtpConfig? Smtp { get; init; }
public MailjetConfig? Mailjet { get; init; }
public SmtpConfig? Smtp { get; init; }

public enum MailType
{
Expand All @@ -35,11 +33,11 @@ public enum MailType
public sealed class SmtpConfig
{
[Required(AllowEmptyStrings = false)] public required string Host { get; init; }
public required int Port { get; init; } = 587;
public required string Username { get; init; } = string.Empty;
public required string Password { get; init; } = string.Empty;
public required bool EnableSsl { get; init; } = true;
public required bool VerifyCertificate { get; init; } = true;
public int Port { get; init; } = 587;
public string Username { get; init; } = string.Empty;
public string Password { get; init; } = string.Empty;
public bool EnableSsl { get; init; } = true;
public bool VerifyCertificate { get; init; } = true;
}

public sealed class MailjetConfig
Expand All @@ -59,18 +57,6 @@ public sealed class TemplateConfig
}
}

public sealed class DbConfig
{
[Required(AllowEmptyStrings = true)] public required string Conn { get; init; }
public required bool SkipMigration { get; init; } = false;
public required bool Debug { get; init; } = false;
}

public sealed class RedisConfig
{
public required string Host { get; init; } = string.Empty;
public required string User { get; init; } = string.Empty;
public required string Password { get; init; } = string.Empty;
public required ushort Port { get; init; } = 6379;
}

}
8 changes: 6 additions & 2 deletions API/Controller/Account/Login.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ public sealed partial class AccountController
[ProducesSuccess]
[ProducesProblem(HttpStatusCode.Unauthorized, "InvalidCredentials")]
[MapToApiVersion("1")]
public async Task<IActionResult> Login([FromBody] Login body, [FromServices] IAccountService accountService, CancellationToken cancellationToken)
public async Task<IActionResult> Login(
[FromBody] Login body,
[FromServices] IAccountService accountService,
[FromServices] ApiConfig apiConfig,
CancellationToken cancellationToken)
{
var loginAction = await accountService.Login(body.Email, body.Password, new LoginContext
{
Expand All @@ -36,7 +40,7 @@ public async Task<IActionResult> Login([FromBody] Login body, [FromServices] IAc
Secure = true,
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Domain = "." + APIGlobals.ApiConfig.CookieDomain
Domain = "." + apiConfig.Frontend.CookieDomain
});

return RespondSuccessSimple("Successfully logged in");
Expand Down
9 changes: 7 additions & 2 deletions API/Controller/Account/LoginV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ public sealed partial class AccountController
[ProducesSuccess]
[ProducesProblem(HttpStatusCode.Unauthorized, "InvalidCredentials")]
[MapToApiVersion("2")]
public async Task<IActionResult> LoginV2([FromBody] LoginV2 body, [FromServices] IAccountService accountService, [FromServices] ICloudflareTurnstileService turnstileService, CancellationToken cancellationToken)
public async Task<IActionResult> LoginV2(
[FromBody] LoginV2 body,
[FromServices] IAccountService accountService,
[FromServices] ICloudflareTurnstileService turnstileService,
[FromServices] ApiConfig apiConfig,
CancellationToken cancellationToken)
{
var turnStile = await turnstileService.VerifyUserResponseToken(body.TurnstileResponse, HttpContext.Connection.RemoteIpAddress, cancellationToken);
if (!turnStile.IsT0) return Problem(TurnstileError.InvalidTurnstile);
Expand All @@ -39,7 +44,7 @@ public async Task<IActionResult> LoginV2([FromBody] LoginV2 body, [FromServices]
Secure = true,
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Domain = "." + APIGlobals.ApiConfig.CookieDomain
Domain = "." + apiConfig.Frontend.CookieDomain
});

return RespondSuccessSimple("Successfully logged in");
Expand Down
10 changes: 7 additions & 3 deletions API/Controller/Account/SignupV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed partial class AccountController
/// <param name="body"></param>
/// <param name="accountService"></param>
/// <param name="turnstileService"></param>
/// <param name="apiConfig"></param>
/// <param name="cancellationToken"></param>
/// <response code="200">User successfully signed up</response>
/// <response code="400">Username or email already exists</response>
Expand All @@ -25,11 +26,14 @@ public sealed partial class AccountController
[ProducesProblem(HttpStatusCode.Conflict, "EmailOrUsernameAlreadyExists")]
[ProducesProblem(HttpStatusCode.Forbidden, "InvalidTurnstileResponse")]
[MapToApiVersion("2")]
public async Task<IActionResult> SignUpV2([FromBody] SignUpV2 body,
[FromServices] IAccountService accountService, [FromServices] ICloudflareTurnstileService turnstileService,
public async Task<IActionResult> SignUpV2(
[FromBody] SignUpV2 body,
[FromServices] IAccountService accountService,
[FromServices] ICloudflareTurnstileService turnstileService,
[FromServices] ApiConfig apiConfig,
CancellationToken cancellationToken)
{
if (APIGlobals.ApiConfig.Turnstile.Enabled)
if (apiConfig.Turnstile.Enabled)
{
var turnStile = await turnstileService.VerifyUserResponseToken(body.TurnstileResponse,
HttpContext.Connection.RemoteIpAddress, cancellationToken);
Expand Down
8 changes: 6 additions & 2 deletions API/Controller/Version/_ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public sealed partial class VersionController : OpenShockControllerBase
/// <response code="200">The version was successfully retrieved.</response>
[HttpGet]
[ProducesSuccess<RootResponse>]
public BaseResponse<RootResponse> GetBackendVersion()
public BaseResponse<RootResponse> GetBackendVersion([FromServices] ApiConfig apiConfig)
{

return new BaseResponse<RootResponse>
Expand All @@ -36,7 +36,9 @@ public BaseResponse<RootResponse> GetBackendVersion()
{
Version = OpenShockBackendVersion,
Commit = GitHashAttribute.FullHash,
CurrentTime = DateTimeOffset.UtcNow
CurrentTime = DateTimeOffset.UtcNow,
FrontendUrl = apiConfig.Frontend.BaseUrl,
ShortLinkUrl = apiConfig.Frontend.ShortUrl
}
};
}
Expand All @@ -46,5 +48,7 @@ public class RootResponse
public required string Version { get; set; }
public required string Commit { get; set; }
public required DateTimeOffset CurrentTime { get; set; }
public required Uri FrontendUrl { get; set; }
public required Uri ShortLinkUrl { get; set; }
}
}
9 changes: 6 additions & 3 deletions API/Services/Account/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed class AccountService : IAccountService
private readonly IEmailService _emailService;
private readonly IRedisCollection<LoginSession> _loginSessions;
private readonly ILogger<AccountService> _logger;
private readonly ApiConfig _apiConfig;

/// <summary>
/// DI Constructor
Expand All @@ -33,12 +34,14 @@ public sealed class AccountService : IAccountService
/// <param name="emailService"></param>
/// <param name="redisConnectionProvider"></param>
/// <param name="logger"></param>
/// <param name="apiConfig"></param>
public AccountService(OpenShockContext db, IEmailService emailService,
IRedisConnectionProvider redisConnectionProvider, ILogger<AccountService> logger)
IRedisConnectionProvider redisConnectionProvider, ILogger<AccountService> logger, ApiConfig apiConfig)
{
_db = db;
_emailService = emailService;
_logger = logger;
_apiConfig = apiConfig;
_loginSessions = redisConnectionProvider.RedisCollection<LoginSession>(false);
}

Expand Down Expand Up @@ -95,7 +98,7 @@ public async Task<OneOf<Success<User>, AccountWithEmailOrUsernameExists>> Signup
await _db.SaveChangesAsync();

await _emailService.VerifyEmail(new Contact(email, username),
new Uri(APIGlobals.ApiConfig.FrontendBaseUrl, $"/#/account/activate/{id}/{secret}"));
new Uri(_apiConfig.Frontend.BaseUrl, $"/#/account/activate/{id}/{secret}"));
return new Success<User>(user);
}

Expand Down Expand Up @@ -165,7 +168,7 @@ public async Task<OneOf<Success, TooManyPasswordResets, NotFound>> CreatePasswor
await _db.SaveChangesAsync();

await _emailService.PasswordReset(new Contact(user.User.Email, user.User.Name),
new Uri(APIGlobals.ApiConfig.FrontendBaseUrl, $"/#/account/password/recover/{passwordReset.Id}/{secret}"));
new Uri(_apiConfig.Frontend.BaseUrl, $"/#/account/password/recover/{passwordReset.Id}/{secret}"));

return new Success();
}
Expand Down
53 changes: 30 additions & 23 deletions API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,18 @@ public class Startup
ForwardedForHeaderName = "CF-Connecting-IP"
};

private ApiConfig _apiConfig;

public Startup(IConfiguration configuration)
{
APIGlobals.ApiConfig = configuration.GetChildren()
.FirstOrDefault(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))?
.Get<ApiConfig>() ??
throw new Exception("Couldn't bind config, check config file");
_apiConfig = configuration.GetChildren()
.FirstOrDefault(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))?
.Get<ApiConfig>() ??
throw new Exception("Couldn't bind config, check config file");

var startupLogger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();

MiniValidation.MiniValidator.TryValidate(APIGlobals.ApiConfig, true, true, out var errors);
MiniValidation.MiniValidator.TryValidate(_apiConfig, true, true, out var errors);
if (errors.Count > 0)
{
var sb = new StringBuilder();
Expand All @@ -84,6 +86,8 @@ public Startup(IConfiguration configuration)

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApiConfig>(_apiConfig);

// ----------------- DATABASE -----------------

// How do I do this now with EFCore?!
Expand All @@ -96,8 +100,8 @@ public void ConfigureServices(IServiceCollection services)
#pragma warning restore CS0618
services.AddDbContextPool<OpenShockContext>(builder =>
{
builder.UseNpgsql(APIGlobals.ApiConfig.Db.Conn);
if (APIGlobals.ApiConfig.Db.Debug)
builder.UseNpgsql(_apiConfig.Db.Conn);
if (_apiConfig.Db.Debug)
{
builder.EnableSensitiveDataLogging();
builder.EnableDetailedErrors();
Expand All @@ -106,8 +110,8 @@ public void ConfigureServices(IServiceCollection services)

services.AddPooledDbContextFactory<OpenShockContext>(builder =>
{
builder.UseNpgsql(APIGlobals.ApiConfig.Db.Conn);
if (APIGlobals.ApiConfig.Db.Debug)
builder.UseNpgsql(_apiConfig.Db.Conn);
if (_apiConfig.Db.Debug)
{
builder.EnableSensitiveDataLogging();
builder.EnableDetailedErrors();
Expand All @@ -117,18 +121,18 @@ public void ConfigureServices(IServiceCollection services)

// ----------------- REDIS -----------------


var redisConfig = new ConfigurationOptions
{
AbortOnConnectFail = true,
Password = APIGlobals.ApiConfig.Redis.Password,
User = APIGlobals.ApiConfig.Redis.User,
Password = _apiConfig.Redis.Password,
User = _apiConfig.Redis.User,
Ssl = false,
EndPoints = new EndPointCollection
{
{ APIGlobals.ApiConfig.Redis.Host, APIGlobals.ApiConfig.Redis.Port }
{ _apiConfig.Redis.Host, _apiConfig.Redis.Port }
}
};

services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(redisConfig));
services.AddSingleton<IRedisConnectionProvider, RedisConnectionProvider>();
services.AddSingleton<IRedisPubService, RedisPubService>();
Expand All @@ -139,21 +143,24 @@ public void ConfigureServices(IServiceCollection services)
services.AddScoped<IClientAuthService<LinkUser>, ClientAuthService<LinkUser>>();
services.AddScoped<IClientAuthService<Device>, ClientAuthService<Device>>();
services.AddScoped<ITokenReferenceService<ApiToken>, TokenReferenceService<ApiToken>>();

services.AddSingleton<IGeoLocation, GeoLocation>();

var turnStileConfig = APIGlobals.ApiConfig.Turnstile;

services.AddSingleton(new CloudflareTurnstileOptions
services.AddSingleton(x =>
{
SecretKey = turnStileConfig.SecretKey ?? string.Empty,
SiteKey = turnStileConfig.SiteKey ?? string.Empty
var config = x.GetRequiredService<ApiConfig>();
return new CloudflareTurnstileOptions
{
SecretKey = config.Turnstile.SecretKey ?? string.Empty,
SiteKey = config.Turnstile.SiteKey ?? string.Empty
};
});
services.AddHttpClient<ICloudflareTurnstileService, CloudflareTurnstileService>();


// ----------------- MAIL SETUP -----------------
var emailConfig = APIGlobals.ApiConfig.Mail;
var emailConfig = _apiConfig.Mail;
switch (emailConfig.Type)
{
case ApiConfig.MailConfig.MailType.Mailjet:
Expand Down Expand Up @@ -226,17 +233,17 @@ public void ConfigureServices(IServiceCollection services)
setup.GroupNameFormat = "VVV";
setup.SubstituteApiVersionInUrl = true;
});

services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNameCaseInsensitive = true;
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.Converters.Add(new PermissionTypeConverter());
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

services.AddProblemDetails();

services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
Expand Down Expand Up @@ -320,7 +327,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF
redisConnection.CreateIndex(typeof(LcgNode));


if (!APIGlobals.ApiConfig.Db.SkipMigration)
if (!_apiConfig.Db.SkipMigration)
{
logger.LogInformation("Running database migrations...");
using var scope = app.ApplicationServices.CreateScope();
Expand Down
10 changes: 5 additions & 5 deletions API/Utils/OneWayPolymorphicJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

namespace OpenShock.API.Utils;

public class OneWayPolymorphicJsonConverter<G> : JsonConverter<G>
public class OneWayPolymorphicJsonConverter<T> : JsonConverter<T>
{
public override bool CanConvert(Type typeToConvert)
{
return typeof(G) == typeToConvert; //.IsAssignableFrom(typeToConvert);
return typeof(T) == typeToConvert; //.IsAssignableFrom(typeToConvert);
}

public override G Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
throw new NotSupportedException();

public override void Write(Utf8JsonWriter writer, G value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value, value.GetType());
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value, value!.GetType());
}
Loading

0 comments on commit 39f1c78

Please sign in to comment.