Skip to content
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

Maintenance #114

Merged
merged 7 commits into from
Nov 12, 2024
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
21 changes: 21 additions & 0 deletions docs/containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,24 @@ make to the application.

Alternatively, if you use the hosted versions you can request an API key that allows for higher rate limits
by sponsoring this project! (if you self-host you can generate your own keys too!).

# Disabling the client validation
If you run the container and navigate to it in your browser
you might come across the following message:
```json
{"message":"The X-Super-Client and X-Super-Contact headers are required"}
```

This is because by default the API will validate all requests send `X-Super-Client` and `X-Super-Contact`
headers along. You can disable this behaviour by setting the following flag:
```json
{
"Helldivers": {
"API": {
"ValidateClients": false
}
}
}
```
Or through environment flags with `-e Helldivers__API__ValidateClients`

5 changes: 5 additions & 0 deletions src/Helldivers-2-API/Configuration/ApiConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public sealed class ApiConfiguration
/// </summary>
public string Blacklist { get; set; } = string.Empty;

/// <summary>
/// Whether X-Super-Client and X-Super-Contact headers are validated.
/// </summary>
public bool ValidateClients { get; set; } = true;

/// <summary>
/// Contains the <see cref="AuthenticationConfiguration" /> for the API.
/// </summary>
Expand Down
10 changes: 5 additions & 5 deletions src/Helldivers-2-API/Helldivers-2-API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

<!-- Dependencies for all build configurations -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
<ProjectReference Include="..\Helldivers-2-Models\Helldivers-2-Models.csproj" />
<ProjectReference Include="..\Helldivers-2-Core\Helldivers-2-Core.csproj" />
Expand All @@ -36,12 +36,12 @@

<!-- Only include swagger dependencies in DEBUG builds -->
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="8.0.3">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSwag.AspNetCore" Version="14.0.4" />
<PackageReference Include="NSwag.AspNetCore" Version="14.1.0" />
</ItemGroup>

</Project>
36 changes: 36 additions & 0 deletions src/Helldivers-2-API/Middlewares/RateLimitMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
using Helldivers.API.Extensions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System.Net;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading.RateLimiting;

namespace Helldivers.API.Middlewares;
Expand All @@ -26,6 +29,12 @@ IMemoryCache cache
/// <inheritdoc />
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (IsValidRequest(context) is false)
{
await RejectRequest(context);
return;
}

var limiter = GetRateLimiter(context);
using var lease = await limiter.AcquireAsync(permitCount: 1, context.RequestAborted);
if (limiter.GetStatistics() is { } statistics)
Expand All @@ -45,6 +54,18 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
await next(context);
}

/// <summary>
/// Checks if the request is "valid" (contains the correct X-Super-* headers).
/// </summary>
private bool IsValidRequest(HttpContext context)
{
if (options.Value.ValidateClients is false)
return true;

return context.Request.Headers.ContainsKey(Constants.CLIENT_HEADER_NAME)
&& context.Request.Headers.ContainsKey(Constants.CONTACT_HEADER_NAME);
}

private RateLimiter GetRateLimiter(HttpContext http)
{
if (http.User.Identity?.IsAuthenticated ?? false)
Expand Down Expand Up @@ -86,4 +107,19 @@ private RateLimiter GetRateLimiterForUser(ClaimsPrincipal user)
});
}) ?? throw new InvalidOperationException($"Creating rate limiter failed for {name}");
}

private async Task RejectRequest(HttpContext context)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.Headers.WWWAuthenticate = "X-Super-Client";
context.Response.ContentType = "application/json";

var writer = new Utf8JsonWriter(context.Response.Body);
writer.WriteStartObject();
writer.WritePropertyName("message");
writer.WriteStringValue("The X-Super-Client and X-Super-Contact headers are required");
writer.WriteEndObject();

await writer.FlushAsync(context.RequestAborted);
}
}
1 change: 1 addition & 0 deletions src/Helldivers-2-API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"Helldivers": {
"API": {
"ValidateClients": false,
"Authentication": {
"SigningKey": "I4eGmsXbDXfxlRo5N+w0ToRGN8aWSIaYWbZ2zMFqqnI="
}
Expand Down
2 changes: 1 addition & 1 deletion src/Helldivers-2-Core/Helldivers-2-Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2"/>
<ProjectReference Include="..\Helldivers-2-Models\Helldivers-2-Models.csproj"/>
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private Assignment MapToV1(Dictionary<string, Models.ArrowHead.Assignment> trans
Description: LocalizedMessage.FromStrings(descriptions),
Tasks: invariant.Setting.Tasks.Select(MapToV1).ToList(),
Reward: MapToV1(invariant.Setting.Reward),
Rewards: invariant.Setting.Rewards.Select(MapToV1).ToList(),
Expiration: expiration
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
/// <param name="OverrideBrief">The briefing (description) of this assignment.</param>
/// <param name="TaskDescription">A description of what is expected of Helldivers to complete the assignment.</param>
/// <param name="Tasks">A list of <see cref="Task" />s describing the assignment requirements.</param>
/// <param name="Reward">Contains information on the rewards players willr eceive upon completion.</param>
/// <param name="Reward">Contains information on the reward players will receive upon completion.</param>
/// <param name="Rewards">Contains information on the rewards players will receive upon completion.</param>
/// <param name="Flags">Flags, suspected to be a binary OR'd value, purpose unknown.</param>
public sealed record Setting(
int Type,
Expand All @@ -17,5 +18,6 @@ public sealed record Setting(
string TaskDescription,
List<Task> Tasks,
Reward Reward,
List<Reward> Rewards,
int Flags
);
2 changes: 2 additions & 0 deletions src/Helldivers-2-Models/V1/Assignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Helldivers.Models.V1;
/// <param name="Description">A very short summary of the description.</param>
/// <param name="Tasks">A list of tasks that need to be completed for this assignment.</param>
/// <param name="Reward">The reward for completing the assignment.</param>
/// <param name="Rewards">A list of rewards for completing the assignment.</param>
/// <param name="Expiration">The date when the assignment will expire.</param>
public sealed record Assignment(
long Id,
Expand All @@ -24,5 +25,6 @@ public sealed record Assignment(
LocalizedMessage Description,
List<Task> Tasks,
Reward Reward,
List<Reward> Rewards,
DateTime Expiration
);
18 changes: 18 additions & 0 deletions src/Helldivers-2-SourceGen/Contracts/IJsonParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Helldivers.SourceGen.Contracts;

/// <summary>
/// Interface for parsing JSON strings and generating corresponding C# source code.
/// </summary>
public interface IJsonParser
{
/// <summary>
/// Parses the given source text and generates corresponding C# source code.
/// </summary>
/// <param name="file">The source file to be parsed.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> for cancelling the parse process.</param>
/// <returns>A <see cref="SourceText"/> object representing the generated C# source code.</returns>
SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default);
}
7 changes: 3 additions & 4 deletions src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.4" PrivateAssets="all" LocalSourceGenerators="true" />
<PackageReference Include="System.Text.Json" Version="8.0.5" PrivateAssets="all" LocalSourceGenerators="true" />
<PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" PrivateAssets="all" LocalSourceGenerators="true" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" PrivateAssets="all" LocalSourceGenerators="true" />
</ItemGroup>
Expand Down
50 changes: 50 additions & 0 deletions src/Helldivers-2-SourceGen/Parsers/BaseJsonParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Helldivers.SourceGen.Contracts;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace Helldivers.SourceGen.Parsers;

/// <summary>
/// An abstract base class for parsing JSON data and generating corresponding C# source code.
/// </summary>
public abstract class BaseJsonParser : IJsonParser
{
private const string TEMPLATE = @"// <auto-generated />
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using global::System.Collections.Generic;
using global::Helldivers.Models.Domain.Localization;

namespace Helldivers.Models;

public static partial class Static
{{
/// <summary>Public list of {0} entries from {1}</summary>
public static {2} {0} = {3};
}}";

/// <inheritdoc />
public SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default)
{
var filename = Path.GetFileName(file.Path);
var json = file.GetText(cancellationToken)?.ToString();

var name = Path.GetFileNameWithoutExtension(file.Path);
name = $"{char.ToUpper(name[0])}{name.Substring(1)}";

if (string.IsNullOrWhiteSpace(json) is false)
{
var (type, csharp) = Parse(json!);

var output = string.Format(TEMPLATE, name, filename, type, csharp);
return SourceText.From(output, Encoding.UTF8);
}

return SourceText.From("// Could not read JSON file");
}

/// <summary>
/// Convert the JSON string into C# code that can be injected.
/// </summary>
protected abstract (string Type, string Source) Parse(string json);
}
22 changes: 22 additions & 0 deletions src/Helldivers-2-SourceGen/Parsers/BiomesParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text;
using System.Text.Json;

namespace Helldivers.SourceGen.Parsers;

/// <summary>
/// BiomesParser is responsible for parsing a JSON string representation of biomes.
/// </summary>
public class BiomesParser : BaseJsonParser
{
/// <inheritdoc />
protected override (string Type, string Source) Parse(string json)
{
var builder = new StringBuilder("new Dictionary<string, Helldivers.Models.V1.Planets.Biome>()\n\t{\n");
var entries = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(json)!;
foreach (var pair in entries)
builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Biome(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");

builder.Append("\t}");
return ("IReadOnlyDictionary<string, Helldivers.Models.V1.Planets.Biome>", builder.ToString());
}
}
23 changes: 23 additions & 0 deletions src/Helldivers-2-SourceGen/Parsers/EnvironmentalsParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text;
using System.Text.Json;

namespace Helldivers.SourceGen.Parsers;

/// <summary>
/// The EnvironmentalsParser class is responsible for parsing JSON strings
/// representing environmental hazards and converting them into C# source code.
/// </summary>
public class EnvironmentalsParser : BaseJsonParser
{
/// <inheritdoc />
protected override (string Type, string Source) Parse(string json)
{
var builder = new StringBuilder("new Dictionary<string, Helldivers.Models.V1.Planets.Hazard>()\n\t{\n");
var entries = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(json)!;
foreach (var pair in entries)
builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Hazard(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");

builder.Append("\t}");
return ("IReadOnlyDictionary<string, Helldivers.Models.V1.Planets.Hazard>", builder.ToString());
}
}
22 changes: 22 additions & 0 deletions src/Helldivers-2-SourceGen/Parsers/FactionsParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text;
using System.Text.Json;

namespace Helldivers.SourceGen.Parsers;

/// <summary>
/// Parses JSON data of factions and generates the corresponding C# source code representation.
/// </summary>
public class FactionsParser : BaseJsonParser
{
/// <inheritdoc />
protected override (string Type, string Source) Parse(string json)
{
var builder = new StringBuilder("new Dictionary<int, string>()\n\t{\n");
var entries = JsonSerializer.Deserialize<Dictionary<string, string>>(json)!;
foreach (var pair in entries)
builder.AppendLine($@"{'\t'}{'\t'}{{ {pair.Key}, ""{pair.Value}"" }},");

builder.Append("\t}");
return ("IReadOnlyDictionary<int, string>", builder.ToString());
}
}
40 changes: 40 additions & 0 deletions src/Helldivers-2-SourceGen/Parsers/PlanetsParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Text;
using System.Text.Json;

namespace Helldivers.SourceGen.Parsers;

/// <summary>
/// Handles parsing the planets.json and generating the resulting planet data.
/// </summary>
public class PlanetsParser : BaseJsonParser
{
/// <inheritdoc />
protected override (string Type, string Source) Parse(string json)
{
var builder = new StringBuilder("new Dictionary<int, (LocalizedMessage Name, string Sector, string Biome, List<string> Environmentals)>()\n\t{\n");
var document = JsonDocument.Parse(json);
foreach (var property in document.RootElement.EnumerateObject())
{
var index = property.Name;
var name = property.Value.GetProperty("name").GetString();
var names = property
.Value
.GetProperty("names")
.EnumerateObject()
.ToDictionary(prop => prop.Name, prop => prop.Value.GetString()!);
var sector = property.Value.GetProperty("sector").GetString();
var biome = property.Value.GetProperty("biome").GetString();
var environmentals = property
.Value
.GetProperty("environmentals")
.EnumerateArray()
.Select(prop => $@"""{prop.GetString()!}""")
.ToList();

builder.AppendLine($@"{'\t'}{'\t'}{{ {index}, (LocalizedMessage.FromStrings([{string.Join(", ", names.Select(pair => $@"new KeyValuePair<string, string>(""{pair.Key}"", ""{pair.Value}"")"))}]), ""{sector}"", ""{biome}"", [{string.Join(", ", environmentals)}]) }},");
}

builder.Append("\t}");
return ("IReadOnlyDictionary<int, (LocalizedMessage Name, string Sector, string Biome, List<string> Environmentals)>", builder.ToString());
}
}
Loading