Add remaining project files
This commit is contained in:
+395
@@ -0,0 +1,395 @@
|
||||
using DeluxeBackend.Hubs;
|
||||
using DeluxeBackend.Services;
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using LiteDB;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.HttpLogging;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using Scalar.AspNetCore;
|
||||
|
||||
|
||||
namespace DeluxeBackend
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static readonly SemaphoreSlim FileLock = new SemaphoreSlim(1, 1);
|
||||
private static Task<string> GenerateSignatureBytes(string signingKey, string uri, byte[] bodyBytes)
|
||||
{
|
||||
byte[] signingKeyBytes = Convert.FromBase64String(signingKey);
|
||||
byte[] uriBytes = Encoding.ASCII.GetBytes(uri);
|
||||
|
||||
using (IncrementalHash incrementalHash = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA256, signingKeyBytes))
|
||||
{
|
||||
incrementalHash.AppendData(uriBytes);
|
||||
|
||||
if (bodyBytes != null && bodyBytes.Length != 0)
|
||||
{
|
||||
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
|
||||
|
||||
if (bodyBytes.Length > 2048)
|
||||
{
|
||||
//Console.WriteLine("Body length > 2048, doing large payload sampling.");
|
||||
|
||||
int sectionSize = bodyBytes.Length / 16;
|
||||
|
||||
//Console.WriteLine($"Appending the first 128 bytes of 16 sections each sized {sectionSize} bytes.");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int currentSection = i * sectionSize;
|
||||
incrementalHash.AppendData(bodyBytes, currentSection, 128);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
incrementalHash.AppendData(bodyBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine("Body Length is 0, not appending any body data.");
|
||||
}
|
||||
|
||||
string base64Hash = Convert.ToBase64String(incrementalHash.GetHashAndReset());
|
||||
return Task.FromResult(base64Hash);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<string> GenerateResponseSignatureRsa(RSAParameters rsaParams, string url, int statusCode, byte[] bodyBytes)
|
||||
{
|
||||
byte[] urlBytes = Encoding.UTF8.GetBytes(url);
|
||||
byte[] statusBytes = Encoding.UTF8.GetBytes(statusCode.ToString());
|
||||
|
||||
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
|
||||
incrementalHash.AppendData(urlBytes);
|
||||
incrementalHash.AppendData(statusBytes);
|
||||
|
||||
if (bodyBytes != null && bodyBytes.Length != 0)
|
||||
{
|
||||
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
|
||||
|
||||
if (bodyBytes.Length > 2048)
|
||||
{
|
||||
int sectionSize = bodyBytes.Length / 16;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int currentSection = i * sectionSize;
|
||||
incrementalHash.AppendData(bodyBytes, currentSection, 128);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
incrementalHash.AppendData(bodyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] hashBytes = incrementalHash.GetHashAndReset();
|
||||
|
||||
using RSA rsa = RSA.Create();
|
||||
rsa.ImportParameters(rsaParams);
|
||||
byte[] signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
|
||||
return Task.FromResult(Convert.ToBase64String(signatureBytes));
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
//JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var corsPolicyName = "AllowAll";
|
||||
|
||||
string base64Key = "RXq0pIk5tTHbKnvWVoUTGUWW3dWt5hdqcTyZ8V71lvI=";
|
||||
builder.Configuration["JwtConfig:Key"] = base64Key;
|
||||
|
||||
builder.Services.AddHttpLogging(logging =>
|
||||
{
|
||||
logging.LoggingFields = HttpLoggingFields.All;
|
||||
logging.RequestHeaders.Add("Authorization");
|
||||
logging.RequestHeaders.Add("Cookie");
|
||||
logging.ResponseHeaders.Add("WWW-Authenticate");
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddSingleton<ILiteDbService, LiteDbService>();
|
||||
builder.Services.AddSingleton<IJwtService, JwtService>();
|
||||
builder.Services.AddSingleton<IPasswordService, PasswordService>();
|
||||
builder.Services.AddSingleton<INotificationService, NotificationService>();
|
||||
builder.Services.AddSingleton<IMessageService, MessageService>();
|
||||
builder.Services.AddSingleton<ICdnService, CdnService>();
|
||||
|
||||
builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
|
||||
{
|
||||
GatewayIntents = GatewayIntents.All,
|
||||
AlwaysDownloadUsers = true
|
||||
}));
|
||||
|
||||
builder.Services.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()));
|
||||
|
||||
builder.Services.AddSingleton<DiscordBotService>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<DiscordBotService>());
|
||||
|
||||
|
||||
builder.Services.AddControllers().AddJsonOptions(options => {
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||
});
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(options => {
|
||||
options.PayloadSerializerSettings.CheckAdditionalContent = false;
|
||||
});
|
||||
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
|
||||
builder.Services.AddSession(options =>
|
||||
{
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.IsEssential = true;
|
||||
});
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(name: corsPolicyName,
|
||||
policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
#if DEBUG
|
||||
builder.Services.AddOpenApi(options =>
|
||||
{
|
||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||
{
|
||||
document.Info.Title = "API";
|
||||
document.Info.Version = "v1";
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
#endif
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options => {
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.SaveToken = true;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidIssuer = builder.Configuration["JwtConfig:Issuer"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["JwtConfig:Key"])),
|
||||
ValidateIssuer = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidateAudience = false
|
||||
};
|
||||
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var path = context.HttpContext.Request.Path;
|
||||
if (path.StartsWithSegments("/Notifications/hub/v1"))
|
||||
{
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
if (!string.IsNullOrEmpty(accessToken)) context.Token = accessToken;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnAuthenticationFailed = ctx =>
|
||||
{
|
||||
Console.WriteLine(ctx.Exception.ToString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
BsonMapper.Global.RegisterType<DateOnly>(
|
||||
serialize: value => value.ToDateTime(TimeOnly.MinValue),
|
||||
deserialize: bson => DateOnly.FromDateTime(bson.AsDateTime)
|
||||
);
|
||||
|
||||
BsonMapper.Global.RegisterType<DateOnly?>(
|
||||
serialize: value => value?.ToDateTime(TimeOnly.MinValue),
|
||||
deserialize: bson => bson.IsNull ? null : DateOnly.FromDateTime(bson.AsDateTime)
|
||||
);
|
||||
BsonMapper.Global.RegisterType<System.Drawing.Color>(
|
||||
serialize: (color) => color.ToArgb(),
|
||||
deserialize: (bson) => System.Drawing.Color.FromArgb(bson.AsInt32)
|
||||
);
|
||||
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseHttpLogging();
|
||||
/*app.MapOpenApi();
|
||||
app.MapScalarApiReference("/docs", options =>
|
||||
{
|
||||
options.HideDownloadButton = true;
|
||||
options.Theme = ScalarTheme.DeepSpace;
|
||||
options.Layout = ScalarLayout.Classic;
|
||||
options.HideDarkModeToggle = true;
|
||||
options.DarkMode = true;
|
||||
});*/
|
||||
}
|
||||
|
||||
app.Use(async (context, next) =>//this is for when rr fucks the uri builder in 2018 builds
|
||||
{
|
||||
var path = context.Request.Path.Value;
|
||||
if (!string.IsNullOrEmpty(path) && path.Contains("//"))
|
||||
{
|
||||
context.Request.Path = "/" + path.TrimStart('/');
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
app.UseCors(corsPolicyName);
|
||||
|
||||
|
||||
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
/*app.Use(async (context, next) =>
|
||||
{
|
||||
string userAgent = context.Request.Headers["User-Agent"].ToString();
|
||||
|
||||
if (userAgent != "BestHTTP/Deluxe")
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
var originalBodyStream = context.Response.Body;
|
||||
|
||||
using (var responseBodyMemoryStream = new MemoryStream())
|
||||
{
|
||||
context.Response.Body = responseBodyMemoryStream;
|
||||
|
||||
await next();
|
||||
|
||||
string url = context.Request.Path + context.Request.QueryString;
|
||||
int statusCode = context.Response.StatusCode;
|
||||
|
||||
var headersBuilder = new StringBuilder();
|
||||
foreach (var header in context.Request.Headers.OrderBy(h => h.Key))
|
||||
{
|
||||
headersBuilder.Append($"{header.Key}:{header.Value}\n");
|
||||
}
|
||||
string serializedHeaders = headersBuilder.ToString();
|
||||
|
||||
responseBodyMemoryStream.Position = 0;
|
||||
byte[] responseBodyBytes = responseBodyMemoryStream.ToArray();
|
||||
|
||||
string signature = await GenerateResponseSignatureRsa(
|
||||
ServerConfig.RsaParams,
|
||||
url,
|
||||
statusCode,
|
||||
responseBodyBytes
|
||||
);
|
||||
|
||||
context.Response.Headers["X-Server-Signature"] = signature;
|
||||
|
||||
responseBodyMemoryStream.Position = 0;
|
||||
await responseBodyMemoryStream.CopyToAsync(originalBodyStream);
|
||||
}
|
||||
});*/
|
||||
|
||||
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.User.Identity?.IsAuthenticated == true && context.User.IsInRole("gameClient"))
|
||||
{
|
||||
if (HttpMethods.IsPost(context.Request.Method) || HttpMethods.IsPut(context.Request.Method))
|
||||
{
|
||||
if (!context.Request.Headers.TryGetValue("X-RNSIG", out var clientSignature))
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
context.Request.EnableBuffering();
|
||||
|
||||
byte[] bodyBytes;
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await context.Request.Body.CopyToAsync(ms);
|
||||
bodyBytes = ms.ToArray();
|
||||
context.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
string uri = context.Request.Path;
|
||||
|
||||
string expectedSignature = await GenerateSignatureBytes(ServerConfig.SigKey, uri, bodyBytes);
|
||||
|
||||
if (clientSignature != expectedSignature)
|
||||
{
|
||||
Console.WriteLine($"Signature Mismatch! Client: {clientSignature}, Expected: {expectedSignature}");
|
||||
context.Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
await next();
|
||||
|
||||
if (context.Response.StatusCode >= 400)
|
||||
{
|
||||
try
|
||||
{
|
||||
await FileLock.WaitAsync();
|
||||
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
string method = context.Request.Method;
|
||||
string url = context.Request.Path + context.Request.QueryString;
|
||||
int status = context.Response.StatusCode;
|
||||
|
||||
string logLine = $"[{timestamp}] {status} | {method} {url}{Environment.NewLine}";
|
||||
|
||||
await File.AppendAllTextAsync("error_urls.txt", logLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to write error log to file: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
FileLock.Release();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.MapControllers();
|
||||
app.MapHub<NotificationHub>("/Notifications/hub/v1");
|
||||
if (ServerConfig.avatarItems.Count == 0)
|
||||
{
|
||||
var json = File.ReadAllText(Path.Combine("data", "AvatarItems.json"));
|
||||
ServerConfig.avatarItems.AddRange(System.Text.Json.JsonSerializer.Deserialize<List<AvatarItemDto>>(json));
|
||||
}
|
||||
app.Run();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user