diff --git a/API.IntegrationTests/API.IntegrationTests.csproj b/API.IntegrationTests/API.IntegrationTests.csproj index 06b883f5..e6da6b60 100644 --- a/API.IntegrationTests/API.IntegrationTests.csproj +++ b/API.IntegrationTests/API.IntegrationTests.csproj @@ -21,7 +21,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/API.IntegrationTests/Docker/InMemoryDatabase.cs b/API.IntegrationTests/Docker/InMemoryDatabase.cs index f81e6904..1e71c747 100644 --- a/API.IntegrationTests/Docker/InMemoryDatabase.cs +++ b/API.IntegrationTests/Docker/InMemoryDatabase.cs @@ -2,6 +2,8 @@ using Testcontainers.PostgreSql; using TUnit.Core.Interfaces; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.IntegrationTests.Docker; public sealed class InMemoryDatabase : IAsyncInitializer, IAsyncDisposable @@ -19,7 +21,7 @@ public PostgreSqlContainer Container .WithName($"tunit-postgresql-{Guid.CreateVersion7()}") .WithDatabase("openshock") .WithUsername("openshock") - .WithPassword(CryptoUtils.RandomAlphaNumericString(32)) + .WithPassword(CryptoUtils.RandomString(32)) .Build(); return _container; diff --git a/API.IntegrationTests/Helpers/TestHelper.cs b/API.IntegrationTests/Helpers/TestHelper.cs index 71947eb2..1bfcbb97 100644 --- a/API.IntegrationTests/Helpers/TestHelper.cs +++ b/API.IntegrationTests/Helpers/TestHelper.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Services.Session; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.IntegrationTests.Helpers; public static class TestHelper @@ -126,7 +128,7 @@ public static async Task CreateUserInDb( var db = scope.ServiceProvider.GetRequiredService(); var deviceId = Guid.CreateVersion7(); - var token = CryptoUtils.RandomAlphaNumericString(256); + var token = CryptoUtils.RandomString(256); db.Devices.Add(new Device { Id = deviceId, @@ -151,7 +153,7 @@ public static async Task CreateUserInDb( await using var scope = factory.Services.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService(); - var rawToken = CryptoUtils.RandomAlphaNumericString(AuthConstants.ApiTokenLength); + var rawToken = CryptoUtils.RandomString(AuthConstants.ApiTokenLength); var tokenId = Guid.CreateVersion7(); db.ApiTokens.Add(new ApiToken { diff --git a/API.IntegrationTests/Tests/LcgAssignmentTests.cs b/API.IntegrationTests/Tests/LcgAssignmentTests.cs index 0b93abdf..dccb5e48 100644 --- a/API.IntegrationTests/Tests/LcgAssignmentTests.cs +++ b/API.IntegrationTests/Tests/LcgAssignmentTests.cs @@ -10,6 +10,8 @@ using OpenShock.Common.Utils; using Redis.OM.Contracts; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.IntegrationTests.Tests; public sealed class LcgAssignmentTests @@ -32,7 +34,7 @@ public async Task Setup() // Set up variables _userId = Guid.CreateVersion7(); _hubId = Guid.CreateVersion7(); - _hubToken = CryptoUtils.RandomAlphaNumericString(256); + _hubToken = CryptoUtils.RandomString(256); // Create mock data db.Users.Add(new User diff --git a/API/API.csproj b/API/API.csproj index c61e25fb..bef3b1f9 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -36,7 +36,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/API/Controller/Account/Activate.cs b/API/Controller/Account/Activate.cs index fdd484ed..0f941501 100644 --- a/API/Controller/Account/Activate.cs +++ b/API/Controller/Account/Activate.cs @@ -4,6 +4,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/Authenticated/ChangeEmail.cs b/API/Controller/Account/Authenticated/ChangeEmail.cs index b2e480f6..079e0a63 100644 --- a/API/Controller/Account/Authenticated/ChangeEmail.cs +++ b/API/Controller/Account/Authenticated/ChangeEmail.cs @@ -6,6 +6,10 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account.Authenticated; public sealed partial class AuthenticatedAccountController diff --git a/API/Controller/Account/Authenticated/ChangePassword.cs b/API/Controller/Account/Authenticated/ChangePassword.cs index a42e772f..e8fa6efd 100644 --- a/API/Controller/Account/Authenticated/ChangePassword.cs +++ b/API/Controller/Account/Authenticated/ChangePassword.cs @@ -6,6 +6,10 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account.Authenticated; public sealed partial class AuthenticatedAccountController diff --git a/API/Controller/Account/Authenticated/ChangeUsername.cs b/API/Controller/Account/Authenticated/ChangeUsername.cs index 759fa11c..0d691f0e 100644 --- a/API/Controller/Account/Authenticated/ChangeUsername.cs +++ b/API/Controller/Account/Authenticated/ChangeUsername.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Problems; using System.Net.Mime; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account.Authenticated; public sealed partial class AuthenticatedAccountController diff --git a/API/Controller/Account/Authenticated/Deactivate.cs b/API/Controller/Account/Authenticated/Deactivate.cs index 6e7e8efc..21f62b8b 100644 --- a/API/Controller/Account/Authenticated/Deactivate.cs +++ b/API/Controller/Account/Authenticated/Deactivate.cs @@ -3,6 +3,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account.Authenticated; public sealed partial class AuthenticatedAccountController diff --git a/API/Controller/Account/Login.cs b/API/Controller/Account/Login.cs index 7503c334..9a8291d5 100644 --- a/API/Controller/Account/Login.cs +++ b/API/Controller/Account/Login.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc; using OpenShock.Common.Errors; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/LoginV2.cs b/API/Controller/Account/LoginV2.cs index 43ae9c7c..1ed0c0d1 100644 --- a/API/Controller/Account/LoginV2.cs +++ b/API/Controller/Account/LoginV2.cs @@ -8,6 +8,8 @@ using OpenShock.API.Models.Response; using OpenShock.API.Services.Turnstile; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/PasswordResetCheckValid.cs b/API/Controller/Account/PasswordResetCheckValid.cs index b5f1312f..77df92de 100644 --- a/API/Controller/Account/PasswordResetCheckValid.cs +++ b/API/Controller/Account/PasswordResetCheckValid.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/PasswordResetComplete.cs b/API/Controller/Account/PasswordResetComplete.cs index 78f6f615..e2184936 100644 --- a/API/Controller/Account/PasswordResetComplete.cs +++ b/API/Controller/Account/PasswordResetComplete.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/PasswordResetInitiateV2.cs b/API/Controller/Account/PasswordResetInitiateV2.cs index c7cd8826..1bc1cc31 100644 --- a/API/Controller/Account/PasswordResetInitiateV2.cs +++ b/API/Controller/Account/PasswordResetInitiateV2.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/Signup.cs b/API/Controller/Account/Signup.cs index 109f3142..5b8ffbb9 100644 --- a/API/Controller/Account/Signup.cs +++ b/API/Controller/Account/Signup.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc; using OpenShock.Common.Errors; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/SignupV2.cs b/API/Controller/Account/SignupV2.cs index cf3c2f72..6bb20eba 100644 --- a/API/Controller/Account/SignupV2.cs +++ b/API/Controller/Account/SignupV2.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Options; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/VerifyEmail.cs b/API/Controller/Account/VerifyEmail.cs index 93b840d0..ec76b9aa 100644 --- a/API/Controller/Account/VerifyEmail.cs +++ b/API/Controller/Account/VerifyEmail.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Account; public sealed partial class AccountController diff --git a/API/Controller/Account/_Turnstile.cs b/API/Controller/Account/_Turnstile.cs index cdbbfcd7..9575df49 100644 --- a/API/Controller/Account/_Turnstile.cs +++ b/API/Controller/Account/_Turnstile.cs @@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Mvc; using OpenShock.API.Errors; using OpenShock.API.Services.Turnstile; -using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Problems; namespace OpenShock.API.Controller.Account; diff --git a/API/Controller/Admin/Configuration.cs b/API/Controller/Admin/Configuration.cs index 0753e5bc..bc2f094f 100644 --- a/API/Controller/Admin/Configuration.cs +++ b/API/Controller/Admin/Configuration.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Services.Configuration; using System.Net.Mime; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Admin; public sealed partial class AdminController diff --git a/API/Controller/Admin/GetUsers.cs b/API/Controller/Admin/GetUsers.cs index 01208c36..d940f455 100644 --- a/API/Controller/Admin/GetUsers.cs +++ b/API/Controller/Admin/GetUsers.cs @@ -4,11 +4,13 @@ using OpenShock.Common.Extensions; using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; -using OpenShock.Common.Query; +using OpenShock.Internal.DynamicLinq.Query; using System.ComponentModel.DataAnnotations; using System.Net.Mime; using Z.EntityFramework.Plus; +using OpenShock.Internal.DynamicLinq.Extensions; + namespace OpenShock.API.Controller.Admin; public sealed partial class AdminController @@ -38,7 +40,7 @@ public async Task GetUsers( if (!string.IsNullOrEmpty(orderbyQuery)) { - query = query.ApplyOrderBy(orderbyQuery); + query = OpenShock.Internal.DynamicLinq.Extensions.IQueryableExtensions.ApplyOrderBy(query, orderbyQuery); } else { diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index 2e5b9322..29d13716 100644 --- a/API/Controller/Device/AssignLCG.cs +++ b/API/Controller/Device/AssignLCG.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Utils; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Device; public sealed partial class DeviceController diff --git a/API/Controller/Device/AssignLCGV2.cs b/API/Controller/Device/AssignLCGV2.cs index d7d80ca5..1ca2a1ca 100644 --- a/API/Controller/Device/AssignLCGV2.cs +++ b/API/Controller/Device/AssignLCGV2.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Device; public sealed partial class DeviceController diff --git a/API/Controller/Device/Pair.cs b/API/Controller/Device/Pair.cs index 4203eb92..7b04ad77 100644 --- a/API/Controller/Device/Pair.cs +++ b/API/Controller/Device/Pair.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Device; public sealed partial class DeviceController diff --git a/API/Controller/Devices/DeviceOtaController.cs b/API/Controller/Devices/DeviceOtaController.cs index fa2de294..221fda68 100644 --- a/API/Controller/Devices/DeviceOtaController.cs +++ b/API/Controller/Devices/DeviceOtaController.cs @@ -12,6 +12,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Services.Ota; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Devices; [ApiController] diff --git a/API/Controller/Devices/DevicesController.cs b/API/Controller/Devices/DevicesController.cs index 7320d064..f58e2de5 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -15,6 +15,12 @@ using OpenShock.API.Services.DeviceUpdate; using Redis.OM; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Devices; public sealed partial class DevicesController @@ -112,7 +118,7 @@ public async Task RegenerateDeviceToken([FromRoute] Guid deviceId var device = await _db.Devices.FirstOrDefaultAsync(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId); if (device is null) return Problem(HubError.HubNotFound); - device.Token = CryptoUtils.RandomAlphaNumericString(256); + device.Token = CryptoUtils.RandomString(256); var affected = await _db.SaveChangesAsync(); if (affected <= 0) throw new Exception("Failed to save regenerated token"); @@ -179,7 +185,7 @@ public async Task CreateDeviceV2([FromBody] HubCreateRequest body Id = Guid.CreateVersion7(), OwnerId = CurrentUser.Id, Name = body.Name, - Token = CryptoUtils.RandomAlphaNumericString(256) + Token = CryptoUtils.RandomString(256) }; _db.Devices.Add(device); await _db.SaveChangesAsync(); diff --git a/API/Controller/Devices/GetShockers.cs b/API/Controller/Devices/GetShockers.cs index bbff7340..54fbf8e2 100644 --- a/API/Controller/Devices/GetShockers.cs +++ b/API/Controller/Devices/GetShockers.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Devices; public sealed partial class DevicesController diff --git a/API/Controller/Public/PublicShareController.cs b/API/Controller/Public/PublicShareController.cs index fdcfc560..cfb7b43b 100644 --- a/API/Controller/Public/PublicShareController.cs +++ b/API/Controller/Public/PublicShareController.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Public; public sealed partial class PublicController diff --git a/API/Controller/Sessions/DeleteSessions.cs b/API/Controller/Sessions/DeleteSessions.cs index 74c5b87c..4054aa8f 100644 --- a/API/Controller/Sessions/DeleteSessions.cs +++ b/API/Controller/Sessions/DeleteSessions.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Problems; using System.Net.Mime; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Sessions; public sealed partial class SessionsController diff --git a/API/Controller/Shares/DeleteShareCode.cs b/API/Controller/Shares/DeleteShareCode.cs index 9ebdd702..af903286 100644 --- a/API/Controller/Shares/DeleteShareCode.cs +++ b/API/Controller/Shares/DeleteShareCode.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares; public sealed partial class SharesController diff --git a/API/Controller/Shares/LinkShareCode.cs b/API/Controller/Shares/LinkShareCode.cs index 110580ea..035881d8 100644 --- a/API/Controller/Shares/LinkShareCode.cs +++ b/API/Controller/Shares/LinkShareCode.cs @@ -8,6 +8,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares; public sealed partial class SharesController diff --git a/API/Controller/Shares/Links/AddShocker.cs b/API/Controller/Shares/Links/AddShocker.cs index 7d9be928..d87aceb3 100644 --- a/API/Controller/Shares/Links/AddShocker.cs +++ b/API/Controller/Shares/Links/AddShocker.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController diff --git a/API/Controller/Shares/Links/DeletePublicShare.cs b/API/Controller/Shares/Links/DeletePublicShare.cs index a04dea6d..d4328f07 100644 --- a/API/Controller/Shares/Links/DeletePublicShare.cs +++ b/API/Controller/Shares/Links/DeletePublicShare.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Extensions; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController diff --git a/API/Controller/Shares/Links/EditShocker.cs b/API/Controller/Shares/Links/EditShocker.cs index f1a1408c..7048c3c3 100644 --- a/API/Controller/Shares/Links/EditShocker.cs +++ b/API/Controller/Shares/Links/EditShocker.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController diff --git a/API/Controller/Shares/Links/PauseShocker.cs b/API/Controller/Shares/Links/PauseShocker.cs index aa77390b..61428727 100644 --- a/API/Controller/Shares/Links/PauseShocker.cs +++ b/API/Controller/Shares/Links/PauseShocker.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController diff --git a/API/Controller/Shares/Links/RemoveShocker.cs b/API/Controller/Shares/Links/RemoveShocker.cs index d22baebb..2ed2b54f 100644 --- a/API/Controller/Shares/Links/RemoveShocker.cs +++ b/API/Controller/Shares/Links/RemoveShocker.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Extensions; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.Links; public sealed partial class ShareLinksController diff --git a/API/Controller/Shares/UserShares/CreateShareInvite.cs b/API/Controller/Shares/UserShares/CreateShareInvite.cs index a5a3d257..e1ef67eb 100644 --- a/API/Controller/Shares/UserShares/CreateShareInvite.cs +++ b/API/Controller/Shares/UserShares/CreateShareInvite.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Problems; using Z.EntityFramework.Plus; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.UserShares; public sealed partial class UserSharesController diff --git a/API/Controller/Shares/UserShares/Invites.cs b/API/Controller/Shares/UserShares/Invites.cs index 3e5f74c6..1be3c3f5 100644 --- a/API/Controller/Shares/UserShares/Invites.cs +++ b/API/Controller/Shares/UserShares/Invites.cs @@ -12,6 +12,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.UserShares; file static class QueryHelper diff --git a/API/Controller/Shares/UserShares/UpdateShockerShares.cs b/API/Controller/Shares/UserShares/UpdateShockerShares.cs index 0d9037ab..3076b23f 100644 --- a/API/Controller/Shares/UserShares/UpdateShockerShares.cs +++ b/API/Controller/Shares/UserShares/UpdateShockerShares.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shares.UserShares; public sealed partial class UserSharesController diff --git a/API/Controller/Shockers/EditShocker.cs b/API/Controller/Shockers/EditShocker.cs index 42b3a934..475c977f 100644 --- a/API/Controller/Shockers/EditShocker.cs +++ b/API/Controller/Shockers/EditShocker.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/GetShockerById.cs b/API/Controller/Shockers/GetShockerById.cs index 73e140d3..0148cd93 100644 --- a/API/Controller/Shockers/GetShockerById.cs +++ b/API/Controller/Shockers/GetShockerById.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/GetShockerLogs.cs b/API/Controller/Shockers/GetShockerLogs.cs index e053b12f..a42a9140 100644 --- a/API/Controller/Shockers/GetShockerLogs.cs +++ b/API/Controller/Shockers/GetShockerLogs.cs @@ -13,6 +13,8 @@ using OpenShock.Common.Utils; using OpenShock.Common.Utils.Pagination; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/PauseShocker.cs b/API/Controller/Shockers/PauseShocker.cs index 9c61a214..72d4e95a 100644 --- a/API/Controller/Shockers/PauseShocker.cs +++ b/API/Controller/Shockers/PauseShocker.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/RegisterShocker.cs b/API/Controller/Shockers/RegisterShocker.cs index b472bdce..000f8359 100644 --- a/API/Controller/Shockers/RegisterShocker.cs +++ b/API/Controller/Shockers/RegisterShocker.cs @@ -11,6 +11,10 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/RemoveShocker.cs b/API/Controller/Shockers/RemoveShocker.cs index 8aa9438a..0259b9f1 100644 --- a/API/Controller/Shockers/RemoveShocker.cs +++ b/API/Controller/Shockers/RemoveShocker.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/SendControl.cs b/API/Controller/Shockers/SendControl.cs index df039fa2..66905775 100644 --- a/API/Controller/Shockers/SendControl.cs +++ b/API/Controller/Shockers/SendControl.cs @@ -12,6 +12,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Services; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Shockers/ShockerShares.cs b/API/Controller/Shockers/ShockerShares.cs index 94f0bed2..abb806f7 100644 --- a/API/Controller/Shockers/ShockerShares.cs +++ b/API/Controller/Shockers/ShockerShares.cs @@ -12,6 +12,8 @@ using OpenShock.Common.Errors; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Shockers; public sealed partial class ShockerController diff --git a/API/Controller/Tokens/DeleteToken.cs b/API/Controller/Tokens/DeleteToken.cs index a109e80b..5275ead0 100644 --- a/API/Controller/Tokens/DeleteToken.cs +++ b/API/Controller/Tokens/DeleteToken.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Tokens; [ApiController] diff --git a/API/Controller/Tokens/ReportTokens.cs b/API/Controller/Tokens/ReportTokens.cs index b91c2956..fece31c1 100644 --- a/API/Controller/Tokens/ReportTokens.cs +++ b/API/Controller/Tokens/ReportTokens.cs @@ -11,6 +11,10 @@ using OpenShock.API.Services.Turnstile; using OpenShock.Common.Services.Webhook; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Tokens; public sealed partial class TokensController diff --git a/API/Controller/Tokens/Tokens.cs b/API/Controller/Tokens/Tokens.cs index 5f3786c7..a0740a4f 100644 --- a/API/Controller/Tokens/Tokens.cs +++ b/API/Controller/Tokens/Tokens.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Tokens; public sealed partial class TokensController diff --git a/API/Controller/Users/LookupByName.cs b/API/Controller/Users/LookupByName.cs index 8741d96b..8c025114 100644 --- a/API/Controller/Users/LookupByName.cs +++ b/API/Controller/Users/LookupByName.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Models; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Controller.Users; public sealed partial class UsersController diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index d3ed735a..e6852f0d 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -10,6 +10,8 @@ using System.Reflection; using Microsoft.AspNetCore.Authentication; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.Controller.Version; diff --git a/API/Errors/TurnstileError.cs b/API/Errors/TurnstileError.cs index 37eadd5f..baf7552f 100644 --- a/API/Errors/TurnstileError.cs +++ b/API/Errors/TurnstileError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.Errors; public static class TurnstileError diff --git a/API/Models/Requests/CreateShareRequest.cs b/API/Models/Requests/CreateShareRequest.cs index 93ff451e..0ff034d7 100644 --- a/API/Models/Requests/CreateShareRequest.cs +++ b/API/Models/Requests/CreateShareRequest.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class CreateShareRequest diff --git a/API/Models/Requests/EditTokenRequest.cs b/API/Models/Requests/EditTokenRequest.cs index fd64005a..ebf1d488 100644 --- a/API/Models/Requests/EditTokenRequest.cs +++ b/API/Models/Requests/EditTokenRequest.cs @@ -6,9 +6,9 @@ namespace OpenShock.API.Models.Requests; public class EditTokenRequest { - [StringLength(HardLimits.ApiKeyNameMaxLength, MinimumLength = 1, ErrorMessage = "API token length must be between {1} and {2}")] + [StringLength(ApiHardLimits.ApiKeyNameMaxLength, MinimumLength = 1, ErrorMessage = "API token length must be between {1} and {2}")] public required string Name { get; set; } - [MaxLength(HardLimits.ApiKeyMaxPermissions, ErrorMessage = "API token permissions must be between {1} and {2}")] + [MaxLength(ApiHardLimits.ApiKeyMaxPermissions, ErrorMessage = "API token permissions must be between {1} and {2}")] public List Permissions { get; set; } = [PermissionType.Shockers_Use]; } \ No newline at end of file diff --git a/API/Models/Requests/EditTokenRequestV2.cs b/API/Models/Requests/EditTokenRequestV2.cs index fb50f456..d596f9ff 100644 --- a/API/Models/Requests/EditTokenRequestV2.cs +++ b/API/Models/Requests/EditTokenRequestV2.cs @@ -6,10 +6,10 @@ namespace OpenShock.API.Models.Requests; public class EditTokenRequestV2 { - [StringLength(HardLimits.ApiKeyNameMaxLength, MinimumLength = 1, ErrorMessage = "API token length must be between {1} and {2}")] + [StringLength(ApiHardLimits.ApiKeyNameMaxLength, MinimumLength = 1, ErrorMessage = "API token length must be between {1} and {2}")] public required string Name { get; set; } - [MaxLength(HardLimits.ApiKeyMaxPermissions, ErrorMessage = "API token permissions must be between {1} and {2}")] + [MaxLength(ApiHardLimits.ApiKeyMaxPermissions, ErrorMessage = "API token permissions must be between {1} and {2}")] public required IReadOnlyList Permissions { get; set; } /// diff --git a/API/Models/Requests/HubCreateRequest.cs b/API/Models/Requests/HubCreateRequest.cs index eb30da29..d79ba58a 100644 --- a/API/Models/Requests/HubCreateRequest.cs +++ b/API/Models/Requests/HubCreateRequest.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class HubCreateRequest diff --git a/API/Models/Requests/HubEditRequest.cs b/API/Models/Requests/HubEditRequest.cs index 10440b39..2f9887d0 100644 --- a/API/Models/Requests/HubEditRequest.cs +++ b/API/Models/Requests/HubEditRequest.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class HubEditRequest diff --git a/API/Models/Requests/LoginV2.cs b/API/Models/Requests/LoginV2.cs index 5ce6b5d6..d937f1f2 100644 --- a/API/Models/Requests/LoginV2.cs +++ b/API/Models/Requests/LoginV2.cs @@ -12,6 +12,6 @@ public sealed class LoginV2 public required string UsernameOrEmail { get; set; } [Required(AllowEmptyStrings = false)] - [StringLength(HardLimits.MaxTurnstileResponseTokenLength)] + [StringLength(ApiHardLimits.MaxTurnstileResponseTokenLength)] public required string TurnstileResponse { get; set; } } \ No newline at end of file diff --git a/API/Models/Requests/NewShocker.cs b/API/Models/Requests/NewShocker.cs index 3b1f0be2..db212a5a 100644 --- a/API/Models/Requests/NewShocker.cs +++ b/API/Models/Requests/NewShocker.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class NewShocker diff --git a/API/Models/Requests/PasswordResetRequestV2.cs b/API/Models/Requests/PasswordResetRequestV2.cs index da85137d..4f1745ac 100644 --- a/API/Models/Requests/PasswordResetRequestV2.cs +++ b/API/Models/Requests/PasswordResetRequestV2.cs @@ -9,6 +9,6 @@ public sealed class PasswordResetRequestV2 public required string Email { get; set; } [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = false)] - [System.ComponentModel.DataAnnotations.StringLength(HardLimits.MaxTurnstileResponseTokenLength)] + [System.ComponentModel.DataAnnotations.StringLength(ApiHardLimits.MaxTurnstileResponseTokenLength)] public required string TurnstileResponse { get; set; } } \ No newline at end of file diff --git a/API/Models/Requests/PublicShareCreate.cs b/API/Models/Requests/PublicShareCreate.cs index f56eb801..c21c6dc0 100644 --- a/API/Models/Requests/PublicShareCreate.cs +++ b/API/Models/Requests/PublicShareCreate.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class PublicShareCreate diff --git a/API/Models/Requests/PublicShareEditShocker.cs b/API/Models/Requests/PublicShareEditShocker.cs index 5519e0e8..153450fa 100644 --- a/API/Models/Requests/PublicShareEditShocker.cs +++ b/API/Models/Requests/PublicShareEditShocker.cs @@ -2,6 +2,8 @@ using OpenShock.API.Models.Response; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Requests; public sealed class PublicShareEditShocker diff --git a/API/Models/Requests/ReportTokensRequest.cs b/API/Models/Requests/ReportTokensRequest.cs index 46ced7d7..ad94671f 100644 --- a/API/Models/Requests/ReportTokensRequest.cs +++ b/API/Models/Requests/ReportTokensRequest.cs @@ -2,12 +2,14 @@ using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations; +using OpenShock.Internal.Common.DataAnnotations; + namespace OpenShock.API.Models.Requests; public class ReportTokensRequest { [Required(AllowEmptyStrings = false)] - [StringLength(HardLimits.MaxTurnstileResponseTokenLength)] + [StringLength(ApiHardLimits.MaxTurnstileResponseTokenLength)] public required string TurnstileResponse { get; set; } [MaxLength(512)] diff --git a/API/Models/Requests/SignupV2.cs b/API/Models/Requests/SignupV2.cs index 01c1bd8c..276582ff 100644 --- a/API/Models/Requests/SignupV2.cs +++ b/API/Models/Requests/SignupV2.cs @@ -15,6 +15,6 @@ public sealed class SignUpV2 public required string Email { get; set; } [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = false)] - [System.ComponentModel.DataAnnotations.StringLength(HardLimits.MaxTurnstileResponseTokenLength)] + [System.ComponentModel.DataAnnotations.StringLength(ApiHardLimits.MaxTurnstileResponseTokenLength)] public required string TurnstileResponse { get; set; } } \ No newline at end of file diff --git a/API/Models/Response/ShockerLimits.cs b/API/Models/Response/ShockerLimits.cs index 22f10499..32cfad3e 100644 --- a/API/Models/Response/ShockerLimits.cs +++ b/API/Models/Response/ShockerLimits.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models.Response; public sealed class ShockerLimits diff --git a/API/Models/ShockerControlSettings.cs b/API/Models/ShockerControlSettings.cs index 35e6ad66..bfb23dc3 100644 --- a/API/Models/ShockerControlSettings.cs +++ b/API/Models/ShockerControlSettings.cs @@ -3,6 +3,8 @@ using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Models; /// diff --git a/API/OAuth/OAuthError.cs b/API/OAuth/OAuthError.cs index 123cfa1e..ee22ccc2 100644 --- a/API/OAuth/OAuthError.cs +++ b/API/OAuth/OAuthError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.API.OAuth; public static class OAuthError diff --git a/API/Realtime/RedisSubscriberService.cs b/API/Realtime/RedisSubscriberService.cs index 8c686be8..f12ed7e1 100644 --- a/API/Realtime/RedisSubscriberService.cs +++ b/API/Realtime/RedisSubscriberService.cs @@ -11,6 +11,8 @@ using Redis.OM.Contracts; using StackExchange.Redis; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.Realtime; /// diff --git a/API/Services/Account/AccountService.cs b/API/Services/Account/AccountService.cs index e92926b0..e7542614 100644 --- a/API/Services/Account/AccountService.cs +++ b/API/Services/Account/AccountService.cs @@ -13,6 +13,8 @@ using OpenShock.Common.Utils; using OpenShock.Common.Validation; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.Services.Account; /// @@ -101,7 +103,7 @@ public async Task, AccountWithEmailOrUsernameExists>> Create var user = accountCreate.AsT0.Value; - var token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + var token = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); user.UserActivationRequest = new UserActivationRequest { @@ -165,7 +167,7 @@ public async Task, AccountWithEmailOrUsernameExists>> Create // If email isn't trusted, create an activation request (email verification) if (!isEmailTrusted) { - activationToken = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + activationToken = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); user.UserActivationRequest = new UserActivationRequest { @@ -412,7 +414,7 @@ public async Task= 3) return new TooManyPasswordResets(); - var token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + var token = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); var passwordReset = new UserPasswordReset { Id = Guid.CreateVersion7(), @@ -574,7 +576,7 @@ public async Task x.Email == lowerCaseEmail)) return new EmailAlreadyInUse(); - var token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + var token = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); var emailChange = new UserEmailChange { Id = Guid.CreateVersion7(), diff --git a/API/Services/LCGNodeProvisioner/ILCGNodeProvisioner.cs b/API/Services/LCGNodeProvisioner/ILCGNodeProvisioner.cs index f3d79b9e..82e7e66c 100644 --- a/API/Services/LCGNodeProvisioner/ILCGNodeProvisioner.cs +++ b/API/Services/LCGNodeProvisioner/ILCGNodeProvisioner.cs @@ -1,4 +1,4 @@ -using OpenShock.Common.Geo; +using OpenShock.Internal.Common.Geo; using OpenShock.Common.Redis; namespace OpenShock.API.Services.LCGNodeProvisioner; diff --git a/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs b/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs index a943820f..4db98921 100644 --- a/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs +++ b/API/Services/LCGNodeProvisioner/LCGNodeProvisioner.cs @@ -1,11 +1,13 @@ using Microsoft.EntityFrameworkCore; using OpenShock.Common.Constants; -using OpenShock.Common.Geo; +using OpenShock.Internal.Common.Geo; using OpenShock.Common.Redis; using Redis.OM; using Redis.OM.Contracts; using Redis.OM.Searching; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.API.Services.LCGNodeProvisioner; public sealed class LCGNodeProvisioner : ILCGNodeProvisioner diff --git a/API/Services/Token/ApiTokenService.cs b/API/Services/Token/ApiTokenService.cs index 14404be2..5deb205b 100644 --- a/API/Services/Token/ApiTokenService.cs +++ b/API/Services/Token/ApiTokenService.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Services.RedisPubSub; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.Services.Token; /// @@ -107,7 +109,7 @@ public IAsyncEnumerable ListTokensV2(Guid ownerId) => Tokens(ow /// public async Task CreateTokenV1(Guid userId, IPAddress createdByIp, CreateTokenRequest body) { - var secret = CryptoUtils.RandomAlphaNumericString(AuthConstants.ApiTokenLength); + var secret = CryptoUtils.RandomString(AuthConstants.ApiTokenLength); var tokenDto = new ApiToken { @@ -137,7 +139,7 @@ public async Task CreateTokenV1(Guid userId, IPAddress cre /// public async Task CreateTokenV2(Guid userId, IPAddress createdByIp, CreateTokenRequestV2 body) { - var secret = CryptoUtils.RandomAlphaNumericString(AuthConstants.ApiTokenLength); + var secret = CryptoUtils.RandomString(AuthConstants.ApiTokenLength); var tokenDto = new ApiToken { diff --git a/Common.Tests/Geo/Alpha2CountryCodeTests.cs b/Common.Tests/Geo/Alpha2CountryCodeTests.cs deleted file mode 100644 index c4198875..00000000 --- a/Common.Tests/Geo/Alpha2CountryCodeTests.cs +++ /dev/null @@ -1,163 +0,0 @@ -using OpenShock.Common.Geo; - -namespace OpenShock.Common.Tests.Geo; - -public class Alpha2CountryCodeTests -{ - [Test] - [Arguments("US", 'U', 'S')] - [Arguments("DE", 'D', 'E')] - public async Task ValidCode_ShouldParse(string str, char char1, char char2) - { - // Act - Alpha2CountryCode c = str; - - // Assert - await Assert.That(c.Char1).IsEqualTo(char1); - await Assert.That(c.Char2).IsEqualTo(char2); - } - - [Test] - [Arguments("E")] - [Arguments("INVALID")] - public async Task InvalidCharCount_ShouldThrow_InvalidLength(string str) - { - // Act & Assert - await Assert.That(() => - { - Alpha2CountryCode c = str; - }) - .ThrowsExactly() - .WithMessage("Country code must be exactly 2 uppercase ASCII characters (Parameter 'str')"); - } - - [Test] - [Arguments("us")] - [Arguments("Us")] - [Arguments("uS")] - [Arguments("12")] - [Arguments("U1")] - [Arguments("1U")] - [Arguments("ÆØ")] - [Arguments(":D")] - public async Task InvalidCharTypes_ShouldThrow(string str) - { - // Act & Assert - await Assert.That(() => - { - Alpha2CountryCode c = str; - }) - .ThrowsExactly() - .WithMessage("Country code must be exactly 2 uppercase ASCII characters (Parameter 'str')"); - } - - [Test] - [Arguments("US", 'U', 'S')] - [Arguments("DE", 'D', 'E')] - public async Task TryParseAndValidate_ValidCode_ShouldReturnTrue(string str, char char1, char char2) - { - // Act - var result = Alpha2CountryCode.TryParse(str, out var c); - - // Assert - await Assert.That(result).IsTrue(); - await Assert.That(c.Char1).IsEqualTo(char1); - await Assert.That(c.Char2).IsEqualTo(char2); - } - - [Test] - [Arguments("E")] - [Arguments("INVALID")] - [Arguments("us")] - [Arguments("Us")] - [Arguments("uS")] - [Arguments("12")] - [Arguments("U1")] - [Arguments("1U")] - [Arguments("ÆØ")] - [Arguments(":D")] - [Arguments("")] - [Arguments(" ")] - [Arguments(" ")] - [Arguments(" ")] - public async Task TryParseAndValidate_InvalidCode_ShouldReturnFalse(string str) - { - // Act - var result = Alpha2CountryCode.TryParse(str, out var c); - - // Assert - await Assert.That(result).IsFalse(); - await Assert.That(c == Alpha2CountryCode.UnknownCountry).IsTrue(); - } - - [Test] - [Arguments("US", "US", 0x5553_5553)] - [Arguments("US", "DE", 0x4445_5553)] - [Arguments("DE", "US", 0x4445_5553)] - public async Task GetCombinedHashCode_ShouldReturnCombined(string str1, string str2, int expected) - { - // Act - var result = Alpha2CountryCode.GetCombinedHashCode(str1, str2); - - // Assert - await Assert.That(result).IsEqualTo(expected); - } - - [Test] - [Arguments("US", 0x5553)] - [Arguments("DE", 0x4445)] - [Arguments("NO", 0x4E4F)] - public async Task GetHashcode_ShouldReturnHash(string str, int expected) - { - // Arrange - Alpha2CountryCode code = str; - - // Act - var result = code.GetHashCode(); - - // Assert - await Assert.That(result).IsEqualTo(expected); // "US" - } - - [Test] - public async Task IsUnknown_ShouldReturnTrue() - { - // Arrange - Alpha2CountryCode code = Alpha2CountryCode.UnknownCountry; - - // Act - var result = code.IsUnknown(); - - // Assert - await Assert.That(result).IsTrue(); - } - - [Test] - public async Task UnknownCountry_IsEqualTo_XX() - { - // Arrange - Alpha2CountryCode code = "XX"; - - // Act - var result = code == Alpha2CountryCode.UnknownCountry; - - // Assert - await Assert.That(result).IsTrue(); - } - - [Test] - [Arguments("US")] - [Arguments("DE")] - [Arguments("NO")] - public async Task IsUnknown_Known_ShouldReturnFalse(string str) - { - // Arrange - Alpha2CountryCode code = str; - - // Act - var result = code.IsUnknown(); - - // Assert - await Assert.That(result).IsFalse(); - } -} diff --git a/Common.Tests/Geo/DistanceLookupTests.cs b/Common.Tests/Geo/DistanceLookupTests.cs deleted file mode 100644 index 649b921d..00000000 --- a/Common.Tests/Geo/DistanceLookupTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using OpenShock.Common.Constants; -using OpenShock.Common.Geo; - -namespace OpenShock.Common.Tests.Geo; - -public class DistanceLookupTests -{ - [Test] - [Arguments("US", "US", 0f)] - [Arguments("US", "DE", 7861.5f)] - public async Task TryGetDistanceBetween_ValidCountries(string str1, string str2, float expectedDistance) - { - // Act - var result = DistanceLookup.TryGetDistanceBetween(str1, str2, out var distance); - - // Assert - await Assert.That(result).IsTrue(); - await Assert.That(distance).IsEqualTo(expectedDistance).Within(0.1f); - } - - [Test] - [Arguments("US", "XX")] - [Arguments("XX", "US")] - [Arguments("XX", "XX")] - [Arguments("EZ", "PZ")] - public async Task TryGetDistanceBetween_UnknownCountry(string str1, string str2) - { - // Act - var result = DistanceLookup.TryGetDistanceBetween(str1, str2, out var distance); - - // Assert - await Assert.That(result).IsFalse(); - await Assert.That(distance).IsEqualTo(Distance.DistanceToAndromedaGalaxyInKm); - } -} diff --git a/Common.Tests/Query/ExpressionBuilderTests.cs b/Common.Tests/Query/ExpressionBuilderTests.cs deleted file mode 100644 index 520baa5c..00000000 --- a/Common.Tests/Query/ExpressionBuilderTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -using OpenShock.Common.Query; -using Bogus; - -namespace OpenShock.Common.Tests.Query; - -public class ExpressionBuilderTests -{ - public sealed class TestClass - { - public required Guid Id { get; set; } - public required string Name { get; set; } - public required int Age { get; set; } - public required uint Height { get; set; } - public required bool IsActive { get; set; } - public required DateTime CreatedAt { get; set; } - public required TestEnum Status { get; set; } - public required float Score { get; set; } - public required double Precision { get; set; } - } - - public enum TestEnum - { - Pending, - Active, - Inactive - } - - private readonly TestClass[] _testArray; - - public ExpressionBuilderTests() - { - var faker = new Faker() - .UseSeed(12345) - .RuleFor(t => t.Id, f => f.Random.Guid()) - .RuleFor(t => t.Name, f => f.Name.FullName()) - .RuleFor(t => t.Age, f => f.Random.Int(18, 99)) - .RuleFor(t => t.Height, f => f.Random.UInt()) - .RuleFor(t => t.IsActive, f => f.Random.Bool()) - .RuleFor(t => t.CreatedAt, f => f.Date.Past(10)) - .RuleFor(t => t.Status, f => f.PickRandom()) - .RuleFor(t => t.Score, f => f.Random.Float(0, 100)) - .RuleFor(t => t.Precision, f => f.Random.Double(0, 100)); - - _testArray = faker.Generate(100).ToArray(); - } - - [Test] - public async Task EmptyString_ThrowsException() - { - // Act & Assert - await Assert - .That(() => DBExpressionBuilder.GetFilterExpression("")) - .ThrowsExactly(); - } - - [Test] - public async Task IntegerBounds_ThrowsExceptionOnOverflow() - { - // Act & Assert - await Assert - .That(() => DBExpressionBuilder.GetFilterExpression("age eq 2147483648")) - .ThrowsExactly(); - } - - [Test] - public async Task UnsignedIntegerBounds_ThrowsExceptionOnNegative() - { - // Act & Assert - await Assert - .That(() => DBExpressionBuilder.GetFilterExpression("height eq -1")) - .ThrowsExactly(); - } - - [Test] - public async Task Guid_ExactMatch() - { - // Act - var testGuid = _testArray.First().Id; // Grab a Guid from the test data - var expression = DBExpressionBuilder.GetFilterExpression($"id eq {testGuid}"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Id == testGuid); - } - - [Test] - public async Task Integer_GreaterThanOrEquals() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("age gte 42"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Age >= 42); - } - - [Test] - public async Task Integer_LessThanOrEquals() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("age lte 51"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Age <= 51); - } - - [Test] - public async Task Enum_Equals_ChecksValidValues() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("status eq Active"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Status == TestEnum.Active); - } - - [Test] - public async Task Enum_NotEquals_ChecksValidValues() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("status != Active"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).DoesNotContain(x => x.Status == TestEnum.Active); - } - - [Test] - public async Task Enum_InvalidValue_ThrowsException() - { - // Act & Assert - await Assert - .That(() => DBExpressionBuilder.GetFilterExpression("status eq Invalid")) - .ThrowsExactly(); - } - - [Test] - public async Task Boolean_TrueMatches() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("isActive eq true"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.IsActive); - } - - [Test] - public async Task Boolean_FalseMatches() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("isActive eq false"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.IsActive == false); - } - - [Test] - public async Task DateTime_ExactMatch() - { - // Act - var testDate = _testArray[20].CreatedAt; - var expression = DBExpressionBuilder.GetFilterExpression($"createdAt eq {testDate:O}"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.CreatedAt == testDate); - } - - [Test] - public async Task DateTime_LessThan() - { - // Act - var referenceDate = DateTime.UtcNow.AddMonths(-6); - var expression = DBExpressionBuilder.GetFilterExpression($"createdAt lt {referenceDate:O}"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.CreatedAt < referenceDate); - } - - [Test] - public async Task Float_GreaterThan() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("score gt 50"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Score > 50f); - } - - [Test] - public async Task Double_LessThan() - { - // Act - var expression = DBExpressionBuilder.GetFilterExpression("precision lt 50"); - var result = _testArray.AsQueryable().Where(expression).ToArray(); - - // Assert - await Assert.That(result).ContainsOnly(x => x.Precision < 50f); - } - - [Test] - [Arguments("Age")] - [Arguments("age asc")] - [Arguments("AGE ASC")] - public async Task OrderByAscending_SortsCorrectly(string query) - { - // Arrange - var queryable = _testArray.AsQueryable(); - - // Act - var result = queryable.ApplyOrderBy(query).ToArray(); - - // Assert - await Assert.That(result).IsOrderedBy(x => x.Age); - } - - [Test] - [Arguments("name desc")] - [Arguments("NAME DESC")] - public async Task OrderByDescending_SortsCorrectly(string query) - { - // Arrange - var queryable = _testArray.AsQueryable(); - - // Act - var result = queryable.ApplyOrderBy(query).ToArray(); - - // Assert - await Assert.That(result).IsOrderedByDescending(x => x.Name); - } - - [Test] - [Arguments("age,createdat")] - [Arguments("age asc,createdat asc")] - public async Task ThenByAscending_SortsCorrectly(string query) - { - // Arrange - var queryable = _testArray.AsQueryable(); - - // Act - var result = queryable.ApplyOrderBy(query).ToArray(); - - // Assert - var expected = _testArray.OrderBy(x => x.Age).ThenBy(x => x.CreatedAt).ToArray(); - await Assert.That(result).IsEquivalentTo(expected); - } - - [Test] - [Arguments("age,name desc")] - public async Task ThenByDescending_SortsCorrectly(string query) - { - // Arrange - var queryable = _testArray.AsQueryable(); - - // Act - var result = queryable.ApplyOrderBy(query).ToArray(); - - // Assert - var expected = _testArray.OrderBy(x => x.Age).ThenByDescending(x => x.Name).ToArray(); - await Assert.That(result).IsEquivalentTo(expected); - } -} diff --git a/Common.Tests/Query/QueryStringTokenizerTests.cs b/Common.Tests/Query/QueryStringTokenizerTests.cs deleted file mode 100644 index c7ca5a6a..00000000 --- a/Common.Tests/Query/QueryStringTokenizerTests.cs +++ /dev/null @@ -1,225 +0,0 @@ -using OpenShock.Common.Query; - -namespace OpenShock.Common.Tests.Query; - -public class QueryStringTokenizerTests -{ - [Test] - public async Task EmptyString_ReturnsEmpty() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens(""); - - // Assert - await Assert.That(result).IsEmpty(); - } - - [Test] - public async Task WhiteSpaceString_ReturnsEmpty() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens(" \r\n\t"); - - // Assert - await Assert.That(result).IsEmpty(); - } - - [Test] - public async Task QuotedNewLine_ReturnsNewLine() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'\n'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["\n"]); - } - - [Test] - public async Task SimpleString_ReturnsMatching() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("testing"); - - // Assert - await Assert.That(result).IsEquivalentTo(["testing"]); - } - - [Test] - public async Task NormalUsage_Succeeds() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("username == 'morgan freeman' and age >= 35 and email ilike morgan*freeman@*.com"); - - // Assert - await Assert.That(result).IsEquivalentTo(["username", "==", "morgan freeman", "and", "age", ">=", "35", "and", "email", "ilike", "morgan*freeman@*.com"]); - } - - [Test] - public async Task SurroundingWhitespace_Ignored() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens(" hello "); - - // Assert - await Assert.That(result).IsEquivalentTo(["hello"]); - } - - [Test] - public async Task SpaceSeperatedString_ReturnsMatching() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("testing tokenizer"); - - // Assert - await Assert.That(result).IsEquivalentTo(["testing", "tokenizer"]); - } - - [Test] - public async Task MultiSpaceSeperatedString_ReturnsMatching() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("testing \r \t \n tokenizer"); - - // Assert - await Assert.That(result).IsEquivalentTo(["testing", "tokenizer"]); - } - - [Test] - public async Task UnmatchedQuote_ThrowsException() - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens("'hello world")) - .ThrowsExactly(); - } - - [Test] - public async Task EmptyQuotedString_ParsesAsEmpty() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("''"); - - // Assert - await Assert.That(result).IsEquivalentTo([string.Empty]); - } - - [Test] - public async Task QuotedString_ReturnsMatching() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'testing tokenizer'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["testing tokenizer"]); - } - - [Test] - public async Task MixedQuotedAndUnquotedWords_ParsesCorrectly() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("this 'is a test' string"); - - // Assert - await Assert.That(result).IsEquivalentTo(["this", "is a test", "string"]); - } - - [Test] - public async Task EscapedQuoteInsideQuotedString_ParsesCorrectly() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'This isn\\'t a bug'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["This isn't a bug"]); - } - - [Test] - public async Task EscapeAtEndOfQuotedString_ThrowsException() - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens("'hello world\\'")) - .ThrowsExactly(); - } - - [Test] - public async Task DoubleEscapedBackslash_ParsesCorrectly() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'This has a backslash: \\\\'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["This has a backslash: \\"]); - } - - [Test] - public async Task QuoteInsideUnquotedString_ThrowsException() - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens("This won't work")) - .ThrowsExactly(); - } - - [Test] - public async Task UnquotedEscapeCharacter_ThrowsException() - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens("hello \\ world")) - .ThrowsExactly(); - } - - [Test] - public async Task OnlyEscapeCharacter_ThrowsException() - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens("\\")) - .ThrowsExactly(); - } - - [Test] - public async Task EmbeddedEscapedNewline_ParsesCorrectly() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'hello\\nworld'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["hello\nworld"]); - } - - [Test] - public async Task ConsecutiveQuotedStrings_ParsesSeparately() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens("'hello' 'world'"); - - // Assert - await Assert.That(result).IsEquivalentTo(["hello", "world"]); - } - - [Test] - public async Task EmptyInputWithWhitespace_ReturnsEmpty() - { - // Act - var result = QueryStringTokenizer.ParseQueryTokens(" "); - - // Assert - await Assert.That(result).IsEmpty(); - } - - [Test] - [Arguments("'\\ '")] // Escape followed by space - [Arguments("'hello \\q'")] // Invalid escape character - [Arguments("'\\x'")] // Undefined escape sequence - [Arguments("'test \\u1234'")] // Unicode escape not supported - [Arguments("'hello \\'")] // Dangling backslash at end of quoted string - public async Task InvalidEscapeCharacters_ThrowsException(string invalidString) - { - // Act & Assert - await Assert - .That(() => QueryStringTokenizer.ParseQueryTokens(invalidString)) - .ThrowsExactly(); - } -} \ No newline at end of file diff --git a/Common.Tests/Utils/CryptoUtilsTests.cs b/Common.Tests/Utils/CryptoUtilsTests.cs index f4073f0a..822e24a0 100644 --- a/Common.Tests/Utils/CryptoUtilsTests.cs +++ b/Common.Tests/Utils/CryptoUtilsTests.cs @@ -1,4 +1,4 @@ -using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; namespace OpenShock.Common.Tests.Utils; @@ -9,33 +9,33 @@ public class CryptoUtilsTests [Arguments(10)] [Arguments(64)] [Arguments(256)] - public async Task RandomAlphaNumericString_CorrectLength(int length) + public async Task RandomString_CorrectLength(int length) { - var result = CryptoUtils.RandomAlphaNumericString(length); + var result = CryptoUtils.RandomString(length); await Assert.That(result.Length).IsEqualTo(length); } [Test] - public async Task RandomAlphaNumericString_OnlyAlphaNumericChars() + public async Task RandomString_OnlyAlphaNumericChars() { - var result = CryptoUtils.RandomAlphaNumericString(1000); + var result = CryptoUtils.RandomString(1000); await Assert.That(result.All(c => char.IsLetterOrDigit(c))).IsTrue(); } [Test] - public async Task RandomAlphaNumericString_ContainsVariety() + public async Task RandomString_ContainsVariety() { // With 1000 chars, should have both letters and digits - var result = CryptoUtils.RandomAlphaNumericString(1000); + var result = CryptoUtils.RandomString(1000); await Assert.That(result.Any(char.IsLetter)).IsTrue(); await Assert.That(result.Any(char.IsDigit)).IsTrue(); } [Test] - public async Task RandomAlphaNumericString_TwoCallsProduceDifferentResults() + public async Task RandomString_TwoCallsProduceDifferentResults() { - var a = CryptoUtils.RandomAlphaNumericString(32); - var b = CryptoUtils.RandomAlphaNumericString(32); + var a = CryptoUtils.RandomString(32); + var b = CryptoUtils.RandomString(32); await Assert.That(a).IsNotEqualTo(b); } diff --git a/Common.Tests/Utils/GravatarUtilsTests.cs b/Common.Tests/Utils/GravatarUtilsTests.cs index 4c670c66..26ce41fe 100644 --- a/Common.Tests/Utils/GravatarUtilsTests.cs +++ b/Common.Tests/Utils/GravatarUtilsTests.cs @@ -1,5 +1,7 @@ using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Tests.Utils; public class GravatarUtilsTests diff --git a/Common.Tests/Utils/HashingUtilsTests.cs b/Common.Tests/Utils/HashingUtilsTests.cs deleted file mode 100644 index 698a2a0e..00000000 --- a/Common.Tests/Utils/HashingUtilsTests.cs +++ /dev/null @@ -1,183 +0,0 @@ -using OpenShock.Common.Models; -using OpenShock.Common.Utils; - -namespace OpenShock.Common.Tests.Utils; - -public class HashingUtilsTests -{ - // --- HashSha256 --- - - [Test] - [Arguments("test", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")] - [Arguments("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in", "2fac5f5f1d048a84fbb75c389f4596e05023ac17da4fcf45a5954d2d9a394301")] - public async Task HashSha256(string str, string expectedHash) - { - var result = HashingUtils.HashSha256(str); - await Assert.That(result).IsEqualTo(expectedHash); - } - - [Test] - public async Task HashSha256_EmptyString_ReturnsKnownHash() - { - // SHA-256 of empty string - var result = HashingUtils.HashSha256(""); - await Assert.That(result).IsEqualTo("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - } - - [Test] - public async Task HashSha256_ReturnsLowercaseHex() - { - var result = HashingUtils.HashSha256("test"); - await Assert.That(result).IsEqualTo(result.ToLowerInvariant()); - } - - [Test] - public async Task HashSha256_Returns64CharHex() - { - var result = HashingUtils.HashSha256("anything"); - await Assert.That(result.Length).IsEqualTo(64); - } - - // --- HashPassword / VerifyPassword --- - - [Test] - public async Task HashPassword_VerifyPassword_Roundtrip() - { - var password = "MySecureP@ssw0rd!"; - var hash = HashingUtils.HashPassword(password); - - var result = HashingUtils.VerifyPassword(password, hash); - await Assert.That(result.Verified).IsTrue(); - await Assert.That(result.NeedsRehash).IsFalse(); - } - - [Test] - public async Task HashPassword_StartsWithBcryptPrefix() - { - var hash = HashingUtils.HashPassword("test"); - await Assert.That(hash.StartsWith("bcrypt:")).IsTrue(); - } - - [Test] - public async Task VerifyPassword_WrongPassword_ReturnsFalse() - { - var hash = HashingUtils.HashPassword("correct"); - var result = HashingUtils.VerifyPassword("wrong", hash); - await Assert.That(result.Verified).IsFalse(); - } - - [Test] - public async Task VerifyPassword_InvalidHashFormat_ReturnsFalse() - { - var result = HashingUtils.VerifyPassword("test", "notahash"); - await Assert.That(result.Verified).IsFalse(); - } - - [Test] - public async Task VerifyPassword_EmptyHash_ReturnsFalse() - { - var result = HashingUtils.VerifyPassword("test", ""); - await Assert.That(result.Verified).IsFalse(); - } - - [Test] - public async Task HashPassword_DifferentPasswords_DifferentHashes() - { - var hash1 = HashingUtils.HashPassword("password1"); - var hash2 = HashingUtils.HashPassword("password2"); - await Assert.That(hash1).IsNotEqualTo(hash2); - } - - [Test] - public async Task HashPassword_SamePassword_DifferentSalts() - { - var hash1 = HashingUtils.HashPassword("same"); - var hash2 = HashingUtils.HashPassword("same"); - await Assert.That(hash1).IsNotEqualTo(hash2); - } - - // --- GetPasswordHashingAlgorithm --- - - [Test] - public async Task GetPasswordHashingAlgorithm_BCryptPrefix_ReturnsBCrypt() - { - var result = HashingUtils.GetPasswordHashingAlgorithm("bcrypt:$2a$11$..."); - await Assert.That(result).IsEqualTo(PasswordHashingAlgorithm.BCrypt); - } - - [Test] - public async Task GetPasswordHashingAlgorithm_Pbkdf2Prefix_ReturnsPBKDF2() - { - var result = HashingUtils.GetPasswordHashingAlgorithm("pbkdf2:somehash"); - await Assert.That(result).IsEqualTo(PasswordHashingAlgorithm.PBKDF2); - } - - [Test] - public async Task GetPasswordHashingAlgorithm_UnknownPrefix_ReturnsUnknown() - { - var result = HashingUtils.GetPasswordHashingAlgorithm("argon2:hash"); - await Assert.That(result).IsEqualTo(PasswordHashingAlgorithm.Unknown); - } - - [Test] - public async Task GetPasswordHashingAlgorithm_NoColon_ReturnsUnknown() - { - var result = HashingUtils.GetPasswordHashingAlgorithm("nocolonhere"); - await Assert.That(result).IsEqualTo(PasswordHashingAlgorithm.Unknown); - } - - [Test] - public async Task GetPasswordHashingAlgorithm_Empty_ReturnsUnknown() - { - var result = HashingUtils.GetPasswordHashingAlgorithm(""); - await Assert.That(result).IsEqualTo(PasswordHashingAlgorithm.Unknown); - } - - // --- HashToken / VerifyToken --- - - [Test] - public async Task HashToken_ReturnsSha256OfToken() - { - var token = "my-api-token"; - var hash = HashingUtils.HashToken(token); - var expected = HashingUtils.HashSha256(token); - await Assert.That(hash).IsEqualTo(expected); - } - - [Test] - public async Task VerifyToken_CorrectToken_Verified() - { - var token = "test-token-123"; - var hash = HashingUtils.HashToken(token); - var result = HashingUtils.VerifyToken(token, hash); - await Assert.That(result.Verified).IsTrue(); - await Assert.That(result.NeedsRehash).IsFalse(); - } - - [Test] - public async Task VerifyToken_WrongToken_NotVerified() - { - var hash = HashingUtils.HashToken("correct-token"); - var result = HashingUtils.VerifyToken("wrong-token", hash); - await Assert.That(result.Verified).IsFalse(); - } - - [Test] - public async Task VerifyToken_EmptyToken_NotVerified() - { - var hash = HashingUtils.HashToken("something"); - var result = HashingUtils.VerifyToken("", hash); - await Assert.That(result.Verified).IsFalse(); - } - - [Test] - public async Task VerifyToken_LegacyBcryptHash_NeedsRehash() - { - // Legacy tokens stored with bcrypt (contains '$' in hash) - var token = "legacy-token"; - var bcryptHash = HashingUtils.HashPassword(token); - var result = HashingUtils.VerifyToken(token, bcryptHash); - // Whether verified depends on bcrypt, but NeedsRehash should be true - await Assert.That(result.NeedsRehash).IsTrue(); - } -} \ No newline at end of file diff --git a/Common.Tests/Utils/LatencyEmulatorTests.cs b/Common.Tests/Utils/LatencyEmulatorTests.cs index 09381856..f4eb6c4e 100644 --- a/Common.Tests/Utils/LatencyEmulatorTests.cs +++ b/Common.Tests/Utils/LatencyEmulatorTests.cs @@ -1,6 +1,8 @@ using OpenShock.Common.Utils; // ReSharper disable ObjectCreationAsStatement +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Tests.Utils; public class LatencyEmulatorTests diff --git a/Common.Tests/Utils/MathUtilsTests.cs b/Common.Tests/Utils/MathUtilsTests.cs index 73497fc0..8aa0614e 100644 --- a/Common.Tests/Utils/MathUtilsTests.cs +++ b/Common.Tests/Utils/MathUtilsTests.cs @@ -1,5 +1,7 @@ using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Tests.Utils; public class MathUtilsTests diff --git a/Common.Tests/Validation/CharsetMatchersTests.cs b/Common.Tests/Validation/CharsetMatchersTests.cs deleted file mode 100644 index 4c61e990..00000000 --- a/Common.Tests/Validation/CharsetMatchersTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using OpenShock.Common.Validation; - -namespace OpenShock.Common.Tests.Validation; - -internal class CharsetMatchersTests -{ - private readonly string[] _whitelist = File.ReadAllLines("Validation/DataSets/WhiteList.txt"); - private readonly string[] _blacklist = File.ReadAllLines("Validation/DataSets/BlackList.txt"); - - [Test] - public async Task ContainsUnwanted_Whitelist_ReturnsFalse() - { - foreach (var line in _whitelist) - { - // Skip empty lines - if (string.IsNullOrEmpty(line)) continue; - - // Act - bool result = CharsetMatchers.ContainsUndesiredUserInterfaceCharacters(line); - - // Assert - await Assert.That(result).IsFalse(); - } - } - - [Test] - public async Task ContainsUnwanted_BlackList_AllReturnTrue() - { - foreach (var line in _blacklist) - { - // Skip empty lines - if (string.IsNullOrEmpty(line)) continue; - - // Act - bool result = CharsetMatchers.ContainsUndesiredUserInterfaceCharacters(line); - - // Assert - await Assert.That(result).IsTrue(); - } - } -} diff --git a/Common.Tests/Validation/UsernameValidatorTests.cs b/Common.Tests/Validation/UsernameValidatorTests.cs index d91dc7e6..8de1490e 100644 --- a/Common.Tests/Validation/UsernameValidatorTests.cs +++ b/Common.Tests/Validation/UsernameValidatorTests.cs @@ -1,5 +1,7 @@ using OpenShock.Common.Validation; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.Tests.Validation; internal class UsernameValidatorTests diff --git a/Common/Authentication/Attributes/TokenPermissionAttribute.cs b/Common/Authentication/Attributes/TokenPermissionAttribute.cs index 438af984..f6520ccf 100644 --- a/Common/Authentication/Attributes/TokenPermissionAttribute.cs +++ b/Common/Authentication/Attributes/TokenPermissionAttribute.cs @@ -7,6 +7,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Redis; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Authentication.Attributes; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] diff --git a/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs b/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs index dcad0963..86816cfe 100644 --- a/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs +++ b/Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs @@ -11,6 +11,12 @@ using System.Security.Claims; using System.Text.Encodings.Web; +using OpenShock.Common.JsonSerialization; + +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Authentication.AuthenticationHandlers; public sealed class ApiTokenAuthentication : AuthenticationHandler @@ -88,6 +94,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties { if (Context.Response.HasStarted) return Task.CompletedTask; _authResultError ??= AuthResultError.UnknownError; - return _authResultError.WriteAsJsonAsync(Context); + return _authResultError.WriteAsJsonAsync(Context, JsonOptions.Default, Context.RequestAborted); } } \ No newline at end of file diff --git a/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs b/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs index 84f1d4b2..6f4f0bd7 100644 --- a/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs +++ b/Common/Authentication/AuthenticationHandlers/HubAuthentication.cs @@ -8,6 +8,10 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Problems; +using OpenShock.Common.JsonSerialization; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Authentication.AuthenticationHandlers; /// @@ -82,6 +86,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties { if (Context.Response.HasStarted) return Task.CompletedTask; _authResultError ??= AuthResultError.UnknownError; - return _authResultError.WriteAsJsonAsync(Context); + return _authResultError.WriteAsJsonAsync(Context, JsonOptions.Default, Context.RequestAborted); } } \ No newline at end of file diff --git a/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs b/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs index f00e41f7..1d11e645 100644 --- a/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs +++ b/Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs @@ -12,6 +12,10 @@ using System.Security.Claims; using System.Text.Encodings.Web; +using OpenShock.Common.JsonSerialization; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Authentication.AuthenticationHandlers; public sealed class UserSessionAuthentication : AuthenticationHandler @@ -102,6 +106,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties { if (Context.Response.HasStarted) return Task.CompletedTask; _authResultError ??= AuthResultError.UnknownError; - return _authResultError.WriteAsJsonAsync(Context); + return _authResultError.WriteAsJsonAsync(Context, JsonOptions.Default, Context.RequestAborted); } } \ No newline at end of file diff --git a/Common/Authentication/OpenShockAuthorizationMiddlewareResultHandler.cs b/Common/Authentication/OpenShockAuthorizationMiddlewareResultHandler.cs index b085daa7..ed4b3767 100644 --- a/Common/Authentication/OpenShockAuthorizationMiddlewareResultHandler.cs +++ b/Common/Authentication/OpenShockAuthorizationMiddlewareResultHandler.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Authorization.Policy; using OpenShock.Common.Errors; +using OpenShock.Common.JsonSerialization; + namespace OpenShock.Common.Authentication; public class OpenShockAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler @@ -19,7 +21,7 @@ public Task HandleAsync(RequestDelegate next, HttpContext context, Authorization if (authorizeResult.Forbidden) { var failedRequirements = authorizeResult.AuthorizationFailure?.FailedRequirements.Select(x => x.ToString() ?? "error") ?? []; - return AuthorizationError.PolicyNotMet(failedRequirements).WriteAsJsonAsync(context); + return AuthorizationError.PolicyNotMet(failedRequirements).WriteAsJsonAsync(context, JsonOptions.Default, context.RequestAborted); } return _defaultHandler.HandleAsync(next, context, policy, authorizeResult); diff --git a/Common/CloudflareIPs.targets b/Common/CloudflareIPs.targets deleted file mode 100644 index afd006a6..00000000 --- a/Common/CloudflareIPs.targets +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - "); - sb.AppendLine("// DO NOT EDIT - manual changes are overwritten on the next regeneration."); - sb.AppendLine("// "); - sb.AppendLine("// Cloudflare public proxy IP ranges (https://www.cloudflare.com/ips), baked in as a startup fallback for TrustedProxiesFetcher when the live fetch at application start fails or times out."); - sb.AppendLine("// Regenerated by the FetchCloudflareIPs target in Common.csproj, gated on -p:UpdateCloudflareIPs=true. The Update Cloudflare Proxies GitHub workflow is the only caller that passes that flag."); - sb.AppendLine("// "); - sb.AppendLine($"// Generated: {timestamp}"); - sb.AppendLine($"// IPv4 SHA256: {v4Hash}"); - sb.AppendLine($"// IPv6 SHA256: {v6Hash}"); - sb.AppendLine("using System.Net;"); - sb.AppendLine(); - sb.AppendLine("namespace OpenShock.Common.Utils;"); - sb.AppendLine(); - sb.AppendLine("public static partial class TrustedProxiesFetcher"); - sb.AppendLine("{"); - sb.AppendLine(" private static readonly IPNetwork[] CloudflareNetworks ="); - sb.AppendLine(" ["); - foreach (var line in lines) - { - var slash = line.IndexOf('/'); - if (slash < 0) - throw new System.FormatException($"Cloudflare IP entry '{line}' is missing the '/prefix' suffix."); - - var addressPart = line.Substring(0, slash); - var prefixPart = line.Substring(slash + 1); - - if (!int.TryParse(prefixPart, out var prefix)) - throw new System.FormatException($"Cloudflare IP entry '{line}' has a non-integer prefix '{prefixPart}'."); - - var bytes = System.Net.IPAddress.Parse(addressPart).GetAddressBytes(); - var inv = System.Globalization.CultureInfo.InvariantCulture; - - string address; - if (bytes.Length == 4) - { - // IPAddress(long) packs octet 0 in the low byte, octet 3 in the high byte. - long value = (long)bytes[0] - | ((long)bytes[1] << 8) - | ((long)bytes[2] << 16) - | ((long)bytes[3] << 24); - address = $"new IPAddress(0x{value.ToString("x8", inv)}L)"; - } - else - { - // IPAddress(ReadOnlySpan) — 16 bytes as hex pairs for readability. - var byteList = string.Join(", ", bytes.Select(b => "0x" + b.ToString("x2", inv))); - address = $"new IPAddress([{byteList}])"; - } - - sb.AppendLine($" // {line}"); - sb.AppendLine($" new IPNetwork({address}, prefixLength: {prefix}),"); - sb.AppendLine(); - } - sb.AppendLine(" ];"); - sb.AppendLine("}"); - - File.WriteAllText(OutputFile, sb.ToString()); - } - ]]> - - - - - - - - - - diff --git a/Common/Common.csproj b/Common/Common.csproj index a1eb241f..cda0fb53 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -17,6 +17,9 @@ + + + @@ -35,8 +38,6 @@ - - diff --git a/Common/Constants/ApiHardLimits.cs b/Common/Constants/ApiHardLimits.cs new file mode 100644 index 00000000..e9afd868 --- /dev/null +++ b/Common/Constants/ApiHardLimits.cs @@ -0,0 +1,33 @@ +using OpenShock.Internal.Common.Constants; + +namespace OpenShock.Common.Constants; + +/// +/// Backend-API-specific hard limits. Shared limits live in +/// (OpenShock.Internal.Common package). +/// +public static class ApiHardLimits +{ + public const int EmailProviderDomainMaxLength = 255; + + public const int ApiKeyNameMaxLength = 64; + public const int ApiKeyMaxPermissions = 256; + + public const int HubTokenMaxLength = 256; + + public const int OtaUpdateMessageMaxLength = 128; + + public const int PasswordHashMaxLength = 100; + + public const int UserEmailChangeSecretMaxLength = 128; + public const int UserActivationRequestSecretMaxLength = 128; + public const int PasswordResetSecretMaxLength = 100; + + public const int MaxTurnstileResponseTokenLength = 2048; // https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + + // Don't allow any firmware prior to 2024. + // Ridiculous edgecase: environment reports year at or prior to 2024, revert to 10 year limit just to be on the safe side + public static readonly TimeSpan FirmwareMaxUptime = DateTime.UtcNow.Year <= 2024 ? + TimeSpan.FromDays(365 * 10) : + DateTime.UtcNow - new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); +} diff --git a/Common/Constants/Distance.cs b/Common/Constants/Distance.cs deleted file mode 100644 index 91044905..00000000 --- a/Common/Constants/Distance.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace OpenShock.Common.Constants; - -public static class Distance -{ - public const float DistanceToAndromedaGalaxyInKm = 2.401E19f; -} \ No newline at end of file diff --git a/Common/Constants/HardLimits.cs b/Common/Constants/HardLimits.cs deleted file mode 100644 index 6688536c..00000000 --- a/Common/Constants/HardLimits.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace OpenShock.Common.Constants; - -public static class HardLimits -{ - public const byte MinControlIntensity = 0; - public const byte MaxControlIntensity = 100; - - public const ushort MinControlDuration = 300; - public const ushort MaxControlDuration = ushort.MaxValue; // 65.535 seconds - - public const int UsernameMinLength = 3; - public const int UsernameMaxLength = 32; - - public const int EmailAddressMinLength = 5; // "a@b.c" (5 chars) - public const int EmailAddressMaxLength = 320; // 64 + 1 + 255 (RFC 2821) - public const int EmailProviderDomainMaxLength = 255; - - public const int PasswordMinLength = 12; - public const int PasswordMaxLength = 256; - - public const int UserAgentMaxLength = 1024; - - public const int ApiKeyNameMaxLength = 64; - public const int ApiKeyMaxPermissions = 256; - - public const int HubNameMinLength = 1; - public const int HubNameMaxLength = 64; - public const int HubTokenMaxLength = 256; - - public const int ShockerNameMinLength = 1; - public const int ShockerNameMaxLength = 64; - - public const int PublicShareNameMinLength = 1; - public const int PublicShareNameMaxLength = 64; - - public const int SemVerMaxLength = 64; - public const int IpAddressMaxLength = 40; - public const int Sha256HashHexLength = 64; - - public const int OtaUpdateMessageMaxLength = 128; - - public const int PasswordHashMaxLength = 100; - - public const int UserEmailChangeSecretMaxLength = 128; - public const int UserActivationRequestSecretMaxLength = 128; - public const int PasswordResetSecretMaxLength = 100; - public const int ShockerControlLogCustomNameMaxLength = 64; - - public const int CreateShareRequestMaxShockers = 128; - - public const int MaxHubsPerUser = 4; - public const int MaxShockersPerHub = 11; - public const int MaxShockerControlLogsPerUser = 2048; - - public const int MaxTurnstileResponseTokenLength = 2048; // https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ - - // Don't allow any firmware prior to 2024. - // Ridiculous edgecase: environment reports year at or prior to 2024, revert to 10 year limit just to be on the safe side - public static readonly TimeSpan FirmwareMaxUptime = DateTime.UtcNow.Year <= 2024 ? - TimeSpan.FromDays(365 * 10) : - DateTime.UtcNow - new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); -} diff --git a/Common/DataAnnotations/EmailAddressAttribute.cs b/Common/DataAnnotations/EmailAddressAttribute.cs index cf7a3ec0..40f5577c 100644 --- a/Common/DataAnnotations/EmailAddressAttribute.cs +++ b/Common/DataAnnotations/EmailAddressAttribute.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations.Interfaces; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.DataAnnotations; /// diff --git a/Common/DataAnnotations/PasswordAttribute.cs b/Common/DataAnnotations/PasswordAttribute.cs index ca42b60a..d81cfacb 100644 --- a/Common/DataAnnotations/PasswordAttribute.cs +++ b/Common/DataAnnotations/PasswordAttribute.cs @@ -4,6 +4,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.DataAnnotations.Interfaces; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.DataAnnotations; /// diff --git a/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs b/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs deleted file mode 100644 index 08cb6eea..00000000 --- a/Common/DataAnnotations/StringCollectionItemMaxLengthAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace OpenShock.Common.DataAnnotations; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] -public sealed class StringCollectionItemMaxLengthAttribute : ValidationAttribute -{ - public StringCollectionItemMaxLengthAttribute(int maxLength) - { - MaxLength = maxLength; - } - - public int MaxLength { get; } - - public override bool IsValid(object? value) - { - return value is IEnumerable items && items.All(item => item.Length <= MaxLength); - } -} \ No newline at end of file diff --git a/Common/Errors/AccountActivationError.cs b/Common/Errors/AccountActivationError.cs index 219868fa..14a723a8 100644 --- a/Common/Errors/AccountActivationError.cs +++ b/Common/Errors/AccountActivationError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AccountActivationError diff --git a/Common/Errors/AccountError.cs b/Common/Errors/AccountError.cs index 0f76aab9..b6051d51 100644 --- a/Common/Errors/AccountError.cs +++ b/Common/Errors/AccountError.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Validation; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AccountError diff --git a/Common/Errors/AdminError.cs b/Common/Errors/AdminError.cs index 99fd6d98..0d6e0e7a 100644 --- a/Common/Errors/AdminError.cs +++ b/Common/Errors/AdminError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AdminError diff --git a/Common/Errors/ApiTokenError.cs b/Common/Errors/ApiTokenError.cs index 5007feea..4bd09709 100644 --- a/Common/Errors/ApiTokenError.cs +++ b/Common/Errors/ApiTokenError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ApiTokenError diff --git a/Common/Errors/AssignLcgError.cs b/Common/Errors/AssignLcgError.cs index 5d390f64..e5672e0d 100644 --- a/Common/Errors/AssignLcgError.cs +++ b/Common/Errors/AssignLcgError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AssignLcgError diff --git a/Common/Errors/AuthResultError.cs b/Common/Errors/AuthResultError.cs index 900b617b..2ed97fb4 100644 --- a/Common/Errors/AuthResultError.cs +++ b/Common/Errors/AuthResultError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AuthResultError diff --git a/Common/Errors/AuthorizationError.cs b/Common/Errors/AuthorizationError.cs index fb4d8ef2..a92cfe2a 100644 --- a/Common/Errors/AuthorizationError.cs +++ b/Common/Errors/AuthorizationError.cs @@ -3,6 +3,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Problems.CustomProblems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class AuthorizationError diff --git a/Common/Errors/ConfigurationError.cs b/Common/Errors/ConfigurationError.cs index bafd2337..573a9c34 100644 --- a/Common/Errors/ConfigurationError.cs +++ b/Common/Errors/ConfigurationError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ConfigurationError diff --git a/Common/Errors/ExceptionError.cs b/Common/Errors/ExceptionError.cs deleted file mode 100644 index 0826d281..00000000 --- a/Common/Errors/ExceptionError.cs +++ /dev/null @@ -1,8 +0,0 @@ -using OpenShock.Common.Problems; - -namespace OpenShock.Common.Errors; - -public static class ExceptionError -{ - public static ExceptionProblem Exception => new ExceptionProblem(); -} \ No newline at end of file diff --git a/Common/Errors/ExpressionError.cs b/Common/Errors/ExpressionError.cs index e5d83b50..61d27efd 100644 --- a/Common/Errors/ExpressionError.cs +++ b/Common/Errors/ExpressionError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ExpressionError diff --git a/Common/Errors/GoneError.cs b/Common/Errors/GoneError.cs index 027660fe..c97b4057 100644 --- a/Common/Errors/GoneError.cs +++ b/Common/Errors/GoneError.cs @@ -1,5 +1,5 @@ using System.Net; -using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; namespace OpenShock.Common.Errors; diff --git a/Common/Errors/HubError.cs b/Common/Errors/HubError.cs index acf22147..368a81f6 100644 --- a/Common/Errors/HubError.cs +++ b/Common/Errors/HubError.cs @@ -2,6 +2,10 @@ using OpenShock.Common.Constants; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class HubError diff --git a/Common/Errors/LoginError.cs b/Common/Errors/LoginError.cs index d6f8d10b..28fe723b 100644 --- a/Common/Errors/LoginError.cs +++ b/Common/Errors/LoginError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class LoginError diff --git a/Common/Errors/PairError.cs b/Common/Errors/PairError.cs index 0b69168b..0b7446a4 100644 --- a/Common/Errors/PairError.cs +++ b/Common/Errors/PairError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class PairError diff --git a/Common/Errors/PasswordResetError.cs b/Common/Errors/PasswordResetError.cs index 64f9de6b..a0948d47 100644 --- a/Common/Errors/PasswordResetError.cs +++ b/Common/Errors/PasswordResetError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class PasswordResetError diff --git a/Common/Errors/PublicShareError.cs b/Common/Errors/PublicShareError.cs index 9117bcc9..fad38dfb 100644 --- a/Common/Errors/PublicShareError.cs +++ b/Common/Errors/PublicShareError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class PublicShareError diff --git a/Common/Errors/SessionError.cs b/Common/Errors/SessionError.cs index d5c0512f..0903bcdb 100644 --- a/Common/Errors/SessionError.cs +++ b/Common/Errors/SessionError.cs @@ -1,5 +1,7 @@ using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class SessionError diff --git a/Common/Errors/ShareCodeError.cs b/Common/Errors/ShareCodeError.cs index 6a7b1abb..dd349240 100644 --- a/Common/Errors/ShareCodeError.cs +++ b/Common/Errors/ShareCodeError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ShareCodeError diff --git a/Common/Errors/ShareError.cs b/Common/Errors/ShareError.cs index cb6b912c..c30b9b24 100644 --- a/Common/Errors/ShareError.cs +++ b/Common/Errors/ShareError.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Problems; using OpenShock.Common.Problems.CustomProblems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ShareError diff --git a/Common/Errors/ShockerError.cs b/Common/Errors/ShockerError.cs index 6f3136a7..a83aa7e4 100644 --- a/Common/Errors/ShockerError.cs +++ b/Common/Errors/ShockerError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class ShockerError diff --git a/Common/Errors/SignupError.cs b/Common/Errors/SignupError.cs index 4069048c..95335191 100644 --- a/Common/Errors/SignupError.cs +++ b/Common/Errors/SignupError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class SignupError diff --git a/Common/Errors/UserError.cs b/Common/Errors/UserError.cs index 553a3b59..d13f5b2b 100644 --- a/Common/Errors/UserError.cs +++ b/Common/Errors/UserError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class UserError diff --git a/Common/Errors/WebsocketError.cs b/Common/Errors/WebsocketError.cs index ddd63d88..75fe5a08 100644 --- a/Common/Errors/WebsocketError.cs +++ b/Common/Errors/WebsocketError.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Problems; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Errors; public static class WebsocketError diff --git a/Common/ExceptionHandle/OpenShockExceptionHandler.cs b/Common/ExceptionHandle/OpenShockExceptionHandler.cs deleted file mode 100644 index b0e8c812..00000000 --- a/Common/ExceptionHandle/OpenShockExceptionHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Net; -using Microsoft.AspNetCore.Diagnostics; -using OpenShock.Common.Errors; - -namespace OpenShock.Common.ExceptionHandle; - -public sealed class OpenShockExceptionHandler : IExceptionHandler -{ - private readonly IHostEnvironment _env; - private readonly ILogger _logger; - - public OpenShockExceptionHandler(IHostEnvironment env, ILoggerFactory loggerFactory) - { - _env = env; - _logger = loggerFactory.CreateLogger("RequestInfo"); - } - - public async ValueTask TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken) - { - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - if (_env.IsDevelopment()) - { - await PrintRequestInfo(context); - } - - await ExceptionError.Exception.WriteAsJsonAsync(context, cancellationToken); - return context.Response.HasStarted; - } - - private async Task PrintRequestInfo(HttpContext context) - { - // Rewind our body reader, so we can read it again. - context.Request.Body.Seek(0, SeekOrigin.Begin); - // Used to read from the body stream. - using var stream = new StreamReader(context.Request.Body); - - // Create Dictionaries to be logging in our RequestInfo object for both Header values and Query parameters. - var headers = context.Request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString()); - var queryParams = context.Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()); - - // Create our RequestInfo object. - var requestInfo = new RequestInfo - { - Body = await stream.ReadToEndAsync(), - Headers = headers, - TraceId = context.TraceIdentifier, - Method = context.Request.Method, - Path = context.Request.Path.Value, - Query = queryParams - }; - - // Finally log this object on Information level. - _logger.LogInformation("{@RequestInfo}", requestInfo); - } -} \ No newline at end of file diff --git a/Common/ExceptionHandle/RequestInfo.cs b/Common/ExceptionHandle/RequestInfo.cs deleted file mode 100644 index 3a7874c2..00000000 --- a/Common/ExceptionHandle/RequestInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ReSharper disable UnusedAutoPropertyAccessor.Global -namespace OpenShock.Common.ExceptionHandle; - -public sealed class RequestInfo -{ - public required string? Path { get; set; } - public required IDictionary Query { get; set; } - public required string Body { get; set; } - public required string Method { get; set; } - public required string TraceId { get; set; } - public required IDictionary Headers { get; set; } -} \ No newline at end of file diff --git a/Common/Extensions/ConfigurationExtensions.cs b/Common/Extensions/ConfigurationExtensions.cs index 910941e3..2f670409 100644 --- a/Common/Extensions/ConfigurationExtensions.cs +++ b/Common/Extensions/ConfigurationExtensions.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Utils; using StackExchange.Redis; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Extensions; file sealed class RedisSection diff --git a/Common/Extensions/DictionaryExtensions.cs b/Common/Extensions/DictionaryExtensions.cs deleted file mode 100644 index 1fdea473..00000000 --- a/Common/Extensions/DictionaryExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.InteropServices; - -namespace OpenShock.Common.Extensions; - -public static class DictionaryExtensions -{ - public static void AppendValue(this Dictionary> dictionary, TKey key, TValue value) where TKey : notnull - { - ref var list = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out _); - if (list is null) - { - list = [value]; - return; - } - - list.Add(value); - } -} \ No newline at end of file diff --git a/Common/Extensions/IQueryableExtensions.cs b/Common/Extensions/IQueryableExtensions.cs index e369aada..d6355c45 100644 --- a/Common/Extensions/IQueryableExtensions.cs +++ b/Common/Extensions/IQueryableExtensions.cs @@ -1,10 +1,13 @@ using System.Linq.Expressions; using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; -using OpenShock.Common.Query; + +using OpenShock.Internal.DynamicLinq.Extensions; namespace OpenShock.Common.Extensions; +// ApplyFilter/ApplyOrderBy now live in OpenShock.Internal.DynamicLinq (OpenShock.Internal.DynamicLinq.Extensions). +// These two helpers depend on the User/RoleType domain model and stay local. public static class IQueryableExtensions { public static IQueryable WhereUserIdMatches(this IQueryable source, Expression> userNavigation, Guid userId) @@ -28,11 +31,4 @@ public static IQueryable WhereIsUserOrPrivileged(this IQueryab return WhereUserIdMatches(source, userNavigation, user.Id); } - - public static IQueryable ApplyFilter(this IQueryable query, string filterQuery) where T : class - { - ArgumentException.ThrowIfNullOrWhiteSpace(filterQuery); - - return query.Where(DBExpressionBuilder.GetFilterExpression(filterQuery)); - } } diff --git a/Common/Extensions/SemaphoreSlimExtensions.cs b/Common/Extensions/SemaphoreSlimExtensions.cs deleted file mode 100644 index ff179463..00000000 --- a/Common/Extensions/SemaphoreSlimExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace OpenShock.Common.Extensions; - -file sealed class Releaser(SemaphoreSlim semaphore) : IDisposable -{ - public void Dispose() => semaphore.Release(); -} - -public static class SemaphoreSlimExtensions -{ - public static async Task LockAsyncScoped(this SemaphoreSlim semaphore) - { - await semaphore.WaitAsync(); - return new Releaser(semaphore); - } - public static async Task LockAsyncScoped(this SemaphoreSlim semaphore, CancellationToken cancellationToken) - { - await semaphore.WaitAsync(cancellationToken); - return new Releaser(semaphore); - } -} \ No newline at end of file diff --git a/Common/Geo/Alpha2CountryCode.cs b/Common/Geo/Alpha2CountryCode.cs deleted file mode 100644 index 41bcd767..00000000 --- a/Common/Geo/Alpha2CountryCode.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace OpenShock.Common.Geo; - -public readonly struct Alpha2CountryCode : IEquatable, IComparable -{ - public static readonly Alpha2CountryCode UnknownCountry = new('X', 'X'); - - private readonly ushort _code; - public char Char1 => (char)(_code >> 8); - public char Char2 => (char)(_code & 0xFF); - - private Alpha2CountryCode(char c1, char c2) - { - _code = (ushort)((c1 << 8) | c2); - } - - public static Alpha2CountryCode FromString(ReadOnlySpan str) - { - if (str is not [>= 'A' and <= 'Z', >= 'A' and <= 'Z']) - throw new ArgumentOutOfRangeException(nameof(str), "Country code must be exactly 2 uppercase ASCII characters"); - - return new Alpha2CountryCode(str[0], str[1]); - } - public static Alpha2CountryCode FromString(string str) - { - if (str is not [>= 'A' and <= 'Z', >= 'A' and <= 'Z']) - throw new ArgumentOutOfRangeException(nameof(str), "Country code must be exactly 2 uppercase ASCII characters"); - - return new Alpha2CountryCode(str[0], str[1]); - } - - public static bool TryParse(ReadOnlySpan str, out Alpha2CountryCode code) - { - if (str is not [>= 'A' and <= 'Z', >= 'A' and <= 'Z']) - { - code = UnknownCountry; - return false; - } - - code = FromString(str); - return true; - } - - public static int GetCombinedHashCode(Alpha2CountryCode code1, Alpha2CountryCode code2) - { - ushort a = code1._code; - ushort b = code2._code; - - if (a > b) (b, a) = (a,b); - - return (a << 16) | b; - } - - public bool IsUnknown() => _code == UnknownCountry._code; - - public override int GetHashCode() => _code; - - public override string ToString() => string.Create(2, _code, (span, code) => - { - span[0] = (char)(code >> 8); - span[1] = (char)(code & 0xFF); - }); - - public bool Equals(Alpha2CountryCode other) => _code == other._code; - - public override bool Equals(object? obj) => obj is Alpha2CountryCode other && Equals(other); - - public int CompareTo(Alpha2CountryCode other) => _code.CompareTo(other._code); - - public static bool operator ==(Alpha2CountryCode left, Alpha2CountryCode right) => left.Equals(right); - - public static bool operator !=(Alpha2CountryCode left, Alpha2CountryCode right) => !left.Equals(right); - - public static bool operator <(Alpha2CountryCode left, Alpha2CountryCode right) => left._code < right._code; - - public static bool operator >(Alpha2CountryCode left, Alpha2CountryCode right) => left._code > right._code; - - public static implicit operator Alpha2CountryCode(ReadOnlySpan str) => FromString(str); - public static implicit operator Alpha2CountryCode(string str) => FromString(str); -} \ No newline at end of file diff --git a/Common/Geo/Alpha2CountryCodeAttribute.cs b/Common/Geo/Alpha2CountryCodeAttribute.cs deleted file mode 100644 index deb9dcc4..00000000 --- a/Common/Geo/Alpha2CountryCodeAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace OpenShock.Common.Geo; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] -public sealed class Alpha2CountryCodeAttribute : ValidationAttribute -{ - /// - protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) - { - if (value is not string asString) - return new ValidationResult("Input type must be string"); - - if (asString is not [>= 'A' and <= 'Z', >= 'A' and <= 'Z']) - return new ValidationResult("Input string must be exactly 2 uppercase characters"); - - if (!Alpha2CountryCode.TryParse(asString, out var countryCode)) - return new ValidationResult($"Failed to create {nameof(Alpha2CountryCode)}"); - - if (!countryCode.IsUnknown() && !CountryInfo.CodeDictionary.ContainsKey(countryCode)) - return new ValidationResult("Country does not exist in mapping"); - - return ValidationResult.Success; - } -} \ No newline at end of file diff --git a/Common/Geo/CountryInfo.cs b/Common/Geo/CountryInfo.cs deleted file mode 100644 index 6da330dd..00000000 --- a/Common/Geo/CountryInfo.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System.Collections.Frozen; - -namespace OpenShock.Common.Geo; - -public sealed class CountryInfo -{ - public CountryInfo(Alpha2CountryCode countryCode, string name, float latitude, float longitude, string? cfRegion) - { - CountryCode = countryCode; - Name = name; - Latitude = latitude; - Longitude = longitude; - CfRegion = cfRegion; - } - - public Alpha2CountryCode CountryCode { get; init; } - public string Name { get; init; } - public float Latitude { get; init; } - public float Longitude { get; init; } - public string? CfRegion { get; init; } - - public static readonly CountryInfo UnknownCountry = new(Alpha2CountryCode.UnknownCountry, "Unknown", 0f, 0f, null); - public static readonly CountryInfo[] Countries = - [ - new("AD", "Andorra", 42.546245f, 1.601554f, "weur"), - new("AE", "United Arab Emirates", 23.424076f, 53.847818f, "apac"), - new("AF", "Afghanistan", 33.93911f, 67.709953f, "apac"), - new("AG", "Antigua and Barbuda", 17.060816f, -61.796428f, "apac"), - new("AI", "Anguilla", 18.220554f, -63.068615f, "apac"), - new("AL", "Albania", 41.153332f, 20.168331f, "weur"), - new("AM", "Armenia", 40.069099f, 45.038189f, "apac"), - new("AN", "Netherlands Antilles", 12.226079f, -69.060087f, null), - new("AO", "Angola", -11.202692f, 17.873887f, "apac"), - new("AQ", "Antarctica", -75.250973f, -0.071389f, "weur"), - new("AR", "Argentina", -38.416097f, -63.616672f, "apac"), - new("AS", "American Samoa", -14.270972f, -170.132217f, "apac"), - new("AT", "Austria", 47.516231f, 14.550072f, "weur"), - new("AU", "Australia", -25.274398f, 133.775136f, "apac"), - new("AW", "Aruba", 12.52111f, -69.968338f, "apac"), - new("AZ", "Azerbaijan", 40.143105f, 47.576927f, "apac"), - new("BA", "Bosnia and Herzegovina", 43.915886f, 17.679076f, "weur"), - new("BB", "Barbados", 13.193887f, -59.543198f, "apac"), - new("BD", "Bangladesh", 23.684994f, 90.356331f, "apac"), - new("BE", "Belgium", 50.503887f, 4.469936f, "weur"), - new("BF", "Burkina Faso", 12.238333f, -1.561593f, "apac"), - new("BG", "Bulgaria", 42.733883f, 25.48583f, "eeur"), - new("BH", "Bahrain", 25.930414f, 50.637772f, "apac"), - new("BI", "Burundi", -3.373056f, 29.918886f, "apac"), - new("BJ", "Benin", 9.30769f, 2.315834f, "apac"), - new("BM", "Bermuda", 32.321384f, -64.75737f, "enam"), - new("BN", "Brunei", 4.535277f, 114.727669f, "apac"), - new("BO", "Bolivia", -16.290154f, -63.588653f, "apac"), - new("BR", "Brazil", -14.235004f, -51.92528f, "apac"), - new("BS", "Bahamas", 25.03428f, -77.39628f, "apac"), - new("BT", "Bhutan", 27.514162f, 90.433601f, "apac"), - new("BV", "Bouvet Island", -54.423199f, 3.413194f, "apac"), - new("BW", "Botswana", -22.328474f, 24.684866f, "apac"), - new("BY", "Belarus", 53.709807f, 27.953389f, "eeur"), - new("BZ", "Belize", 17.189877f, -88.49765f, "apac"), - new("CA", "Canada", 56.130366f, -106.346771f, "enam"), - new("CC", "Cocos [Keeling] Islands", -12.164165f, 96.870956f, "apac"), - new("CD", "Congo [DRC]", -4.038333f, 21.758664f, "apac"), - new("CF", "Central African Republic", 6.611111f, 20.939444f, "apac"), - new("CG", "Congo [Republic]", -0.228021f, 15.827659f, "apac"), - new("CH", "Switzerland", 46.818188f, 8.227512f, "weur"), - new("CI", "Côte d'Ivoire", 7.539989f, -5.54708f, "apac"), - new("CK", "Cook Islands", -21.236736f, -159.777671f, "apac"), - new("CL", "Chile", -35.675147f, -71.542969f, "apac"), - new("CM", "Cameroon", 7.369722f, 12.354722f, "apac"), - new("CN", "China", 35.86166f, 104.195397f, "apac"), - new("CO", "Colombia", 4.570868f, -74.297333f, "apac"), - new("CR", "Costa Rica", 9.748917f, -83.753428f, "apac"), - new("CU", "Cuba", 21.521757f, -77.781167f, "apac"), - new("CV", "Cape Verde", 16.002082f, -24.013197f, "apac"), - new("CX", "Christmas Island", -10.447525f, 105.690449f, "apac"), - new("CY", "Cyprus", 35.126413f, 33.429859f, "apac"), - new("CZ", "Czech Republic", 49.817492f, 15.472962f, "eeur"), - new("DE", "Germany", 51.165691f, 10.451526f, "weur"), - new("DJ", "Djibouti", 11.825138f, 42.590275f, "apac"), - new("DK", "Denmark", 56.26392f, 9.501785f, "weur"), - new("DM", "Dominica", 15.414999f, -61.370976f, "apac"), - new("DO", "Dominican Republic", 18.735693f, -70.162651f, "apac"), - new("DZ", "Algeria", 28.033886f, 1.659626f, "apac"), - new("EC", "Ecuador", -1.831239f, -78.183406f, "apac"), - new("EE", "Estonia", 58.595272f, 25.013607f, "weur"), - new("EG", "Egypt", 26.820553f, 30.802498f, "apac"), - new("EH", "Western Sahara", 24.215527f, -12.885834f, "apac"), - new("ER", "Eritrea", 15.179384f, 39.782334f, "apac"), - new("ES", "Spain", 40.463667f, -3.74922f, "weur"), - new("ET", "Ethiopia", 9.145f, 40.489673f, "apac"), - new("FI", "Finland", 61.92411f, 25.748151f, "weur"), - new("FJ", "Fiji", -16.578193f, 179.414413f, "apac"), - new("FK", "Falkland Islands [Islas Malvinas]", -51.796253f, -59.523613f, "apac"), - new("FM", "Micronesia", 7.425554f, 150.550812f, "apac"), - new("FO", "Faroe Islands", 61.892635f, -6.911806f, "weur"), - new("FR", "France", 46.227638f, 2.213749f, "weur"), - new("GA", "Gabon", -0.803689f, 11.609444f, "apac"), - new("GB", "United Kingdom", 55.378051f, -3.435973f, "weur"), - new("GD", "Grenada", 12.262776f, -61.604171f, "apac"), - new("GE", "Georgia", 42.315407f, 43.356892f, "apac"), - new("GF", "French Guiana", 3.933889f, -53.125782f, "apac"), - new("GG", "Guernsey", 49.465691f, -2.585278f, "weur"), - new("GH", "Ghana", 7.946527f, -1.023194f, "apac"), - new("GI", "Gibraltar", 36.137741f, -5.345374f, "weur"), - new("GL", "Greenland", 71.706936f, -42.604303f, "enam"), - new("GM", "Gambia", 13.443182f, -15.310139f, "apac"), - new("GN", "Guinea", 9.945587f, -9.696645f, "apac"), - new("GP", "Guadeloupe", 16.995971f, -62.067641f, "apac"), - new("GQ", "Equatorial Guinea", 1.650801f, 10.267895f, "apac"), - new("GR", "Greece", 39.074208f, 21.824312f, "weur"), - new("GS", "South Georgia and the South Sandwich Islands", -54.429579f, -36.587909f, "apac"), - new("GT", "Guatemala", 15.783471f, -90.230759f, "apac"), - new("GU", "Guam", 13.444304f, 144.793731f, "apac"), - new("GW", "Guinea-Bissau", 11.803749f, -15.180413f, "apac"), - new("GY", "Guyana", 4.860416f, -58.93018f, "apac"), - new("GZ", "Gaza Strip", 31.354676f, 34.308825f, null), - new("HK", "Hong Kong", 22.396428f, 114.109497f, "apac"), - new("HM", "Heard Island and McDonald Islands", -53.08181f, 73.504158f, "apac"), - new("HN", "Honduras", 15.199999f, -86.241905f, "apac"), - new("HR", "Croatia", 45.1f, 15.2f, "weur"), - new("HT", "Haiti", 18.971187f, -72.285215f, "apac"), - new("HU", "Hungary", 47.162494f, 19.503304f, "eeur"), - new("ID", "Indonesia", -0.789275f, 113.921327f, "apac"), - new("IE", "Ireland", 53.41291f, -8.24389f, "weur"), - new("IL", "Israel", 31.046051f, 34.851612f, "apac"), - new("IM", "Isle of Man", 54.236107f, -4.548056f, "weur"), - new("IN", "India", 20.593684f, 78.96288f, "apac"), - new("IO", "British Indian Ocean Territory", -6.343194f, 71.876519f, "apac"), - new("IQ", "Iraq", 33.223191f, 43.679291f, "apac"), - new("IR", "Iran", 32.427908f, 53.688046f, "apac"), - new("IS", "Iceland", 64.963051f, -19.020835f, "weur"), - new("IT", "Italy", 41.87194f, 12.56738f, "weur"), - new("JE", "Jersey", 49.214439f, -2.13125f, "weur"), - new("JM", "Jamaica", 18.109581f, -77.297508f, "apac"), - new("JO", "Jordan", 30.585164f, 36.238414f, "apac"), - new("JP", "Japan", 36.204824f, 138.252924f, "apac"), - new("KE", "Kenya", -0.023559f, 37.906193f, "apac"), - new("KG", "Kyrgyzstan", 41.20438f, 74.766098f, "apac"), - new("KH", "Cambodia", 12.565679f, 104.990963f, "apac"), - new("KI", "Kiribati", -3.370417f, -168.734039f, "apac"), - new("KM", "Comoros", -11.875001f, 43.872219f, "apac"), - new("KN", "Saint Kitts and Nevis", 17.357822f, -62.782998f, "apac"), - new("KP", "North Korea", 40.339852f, 127.510093f, "apac"), - new("KR", "South Korea", 35.907757f, 127.766922f, "apac"), - new("KW", "Kuwait", 29.31166f, 47.481766f, "apac"), - new("KY", "Cayman Islands", 19.513469f, -80.566956f, "apac"), - new("KZ", "Kazakhstan", 48.019573f, 66.923684f, "apac"), - new("LA", "Laos", 19.85627f, 102.495496f, "apac"), - new("LB", "Lebanon", 33.854721f, 35.862285f, "apac"), - new("LC", "Saint Lucia", 13.909444f, -60.978893f, "apac"), - new("LI", "Liechtenstein", 47.166f, 9.555373f, "weur"), - new("LK", "Sri Lanka", 7.873054f, 80.771797f, "apac"), - new("LR", "Liberia", 6.428055f, -9.429499f, "apac"), - new("LS", "Lesotho", -29.609988f, 28.233608f, "apac"), - new("LT", "Lithuania", 55.169438f, 23.881275f, "weur"), - new("LU", "Luxembourg", 49.815273f, 6.129583f, "weur"), - new("LV", "Latvia", 56.879635f, 24.603189f, "weur"), - new("LY", "Libya", 26.3351f, 17.228331f, "apac"), - new("MA", "Morocco", 31.791702f, -7.09262f, "apac"), - new("MC", "Monaco", 43.750298f, 7.412841f, "weur"), - new("MD", "Moldova", 47.411631f, 28.369885f, "eeur"), - new("ME", "Montenegro", 42.708678f, 19.37439f, "weur"), - new("MG", "Madagascar", -18.766947f, 46.869107f, "apac"), - new("MH", "Marshall Islands", 7.131474f, 171.184478f, "apac"), - new("MK", "Macedonia [FYROM]", 41.608635f, 21.745275f, "weur"), - new("ML", "Mali", 17.570692f, -3.996166f, "apac"), - new("MM", "Myanmar [Burma]", 21.913965f, 95.956223f, "apac"), - new("MN", "Mongolia", 46.862496f, 103.846656f, "apac"), - new("MO", "Macau", 22.198745f, 113.543873f, "apac"), - new("MP", "Northern Mariana Islands", 17.33083f, 145.38469f, "apac"), - new("MQ", "Martinique", 14.641528f, -61.024174f, "apac"), - new("MR", "Mauritania", 21.00789f, -10.940835f, "apac"), - new("MS", "Montserrat", 16.742498f, -62.187366f, "apac"), - new("MT", "Malta", 35.937496f, 14.375416f, "weur"), - new("MU", "Mauritius", -20.348404f, 57.552152f, "apac"), - new("MV", "Maldives", 3.202778f, 73.22068f, "apac"), - new("MW", "Malawi", -13.254308f, 34.301525f, "apac"), - new("MX", "Mexico", 23.634501f, -102.552784f, "apac"), - new("MY", "Malaysia", 4.210484f, 101.975766f, "apac"), - new("MZ", "Mozambique", -18.665695f, 35.529562f, "apac"), - new("NA", "Namibia", -22.95764f, 18.49041f, "apac"), - new("NC", "New Caledonia", -20.904305f, 165.618042f, "apac"), - new("NE", "Niger", 17.607789f, 8.081666f, "apac"), - new("NF", "Norfolk Island", -29.040835f, 167.954712f, "apac"), - new("NG", "Nigeria", 9.081999f, 8.675277f, "apac"), - new("NI", "Nicaragua", 12.865416f, -85.207229f, "apac"), - new("NL", "Netherlands", 52.132633f, 5.291266f, "weur"), - new("NO", "Norway", 60.472024f, 8.468946f, "weur"), - new("NP", "Nepal", 28.394857f, 84.124008f, "apac"), - new("NR", "Nauru", -0.522778f, 166.931503f, "apac"), - new("NU", "Niue", -19.054445f, -169.867233f, "apac"), - new("NZ", "New Zealand", -40.900557f, 174.885971f, "apac"), - new("OM", "Oman", 21.512583f, 55.923255f, "apac"), - new("PA", "Panama", 8.537981f, -80.782127f, "apac"), - new("PE", "Peru", -9.189967f, -75.015152f, "apac"), - new("PF", "French Polynesia", -17.679742f, -149.406843f, "apac"), - new("PG", "Papua New Guinea", -6.314993f, 143.95555f, "apac"), - new("PH", "Philippines", 12.879721f, 121.774017f, "apac"), - new("PK", "Pakistan", 30.375321f, 69.345116f, "apac"), - new("PL", "Poland", 51.919438f, 19.145136f, "eeur"), - new("PM", "Saint Pierre and Miquelon", 46.941936f, -56.27111f, "enam"), - new("PN", "Pitcairn Islands", -24.703615f, -127.439308f, "apac"), - new("PR", "Puerto Rico", 18.220833f, -66.590149f, "apac"), - new("PS", "Palestinian Territories", 31.952162f, 35.233154f, "apac"), - new("PT", "Portugal", 39.399872f, -8.224454f, "weur"), - new("PW", "Palau", 7.51498f, 134.58252f, "apac"), - new("PY", "Paraguay", -23.442503f, -58.443832f, "apac"), - new("QA", "Qatar", 25.354826f, 51.183884f, "apac"), - new("RE", "Réunion", -21.115141f, 55.536384f, "apac"), - new("RO", "Romania", 45.943161f, 24.96676f, "eeur"), - new("RS", "Serbia", 44.016521f, 21.005859f, "weur"), - new("RU", "Russia", 61.52401f, 105.318756f, "eeur"), - new("RW", "Rwanda", -1.940278f, 29.873888f, "apac"), - new("SA", "Saudi Arabia", 23.885942f, 45.079162f, "apac"), - new("SB", "Solomon Islands", -9.64571f, 160.156194f, "apac"), - new("SC", "Seychelles", -4.679574f, 55.491977f, "apac"), - new("SD", "Sudan", 12.862807f, 30.217636f, "apac"), - new("SE", "Sweden", 60.128161f, 18.643501f, "weur"), - new("SG", "Singapore", 1.352083f, 103.819836f, "apac"), - new("SH", "Saint Helena", -24.143474f, -10.030696f, "apac"), - new("SI", "Slovenia", 46.151241f, 14.995463f, "weur"), - new("SJ", "Svalbard and Jan Mayen", 77.553604f, 23.670272f, "weur"), - new("SK", "Slovakia", 48.669026f, 19.699024f, "eeur"), - new("SL", "Sierra Leone", 8.460555f, -11.779889f, "apac"), - new("SM", "San Marino", 43.94236f, 12.457777f, "weur"), - new("SN", "Senegal", 14.497401f, -14.452362f, "apac"), - new("SO", "Somalia", 5.152149f, 46.199616f, "apac"), - new("SR", "Suriname", 3.919305f, -56.027783f, "apac"), - new("ST", "Sao Tomé and Príncipe", 0.18636f, 6.613081f, "apac"), - new("SV", "El Salvador", 13.794185f, -88.89653f, "apac"), - new("SY", "Syria", 34.802075f, 38.996815f, "apac"), - new("SZ", "Swaziland", -26.522503f, 31.465866f, "apac"), - new("TC", "Turks and Caicos Islands", 21.694025f, -71.797928f, "apac"), - new("TD", "Chad", 15.454166f, 18.732207f, "apac"), - new("TF", "French Southern Territories", -49.280366f, 69.348557f, "apac"), - new("TG", "Togo", 8.619543f, 0.824782f, "apac"), - new("TH", "Thailand", 15.870032f, 100.992541f, "apac"), - new("TJ", "Tajikistan", 38.861034f, 71.276093f, "apac"), - new("TK", "Tokelau", -8.967363f, -171.855881f, "apac"), - new("TL", "Timor-Leste", -8.874217f, 125.727539f, "apac"), - new("TM", "Turkmenistan", 38.969719f, 59.556278f, "apac"), - new("TN", "Tunisia", 33.886917f, 9.537499f, "apac"), - new("TO", "Tonga", -21.178986f, -175.198242f, "apac"), - new("TR", "Turkey", 38.963745f, 35.243322f, "apac"), - new("TT", "Trinidad and Tobago", 10.691803f, -61.222503f, "apac"), - new("TV", "Tuvalu", -7.109535f, 177.64933f, "apac"), - new("TW", "Taiwan", 23.69781f, 120.960515f, "apac"), - new("TZ", "Tanzania", -6.369028f, 34.888822f, "apac"), - new("UA", "Ukraine", 48.379433f, 31.16558f, "eeur"), - new("UG", "Uganda", 1.373333f, 32.290275f, "apac"), - new("UM", "U.S. Minor Outlying Islands", 19.295374f, 166.6280441f, "apac"), - new("US", "United States", 37.09024f, -95.712891f, "enam"), - new("UY", "Uruguay", -32.522779f, -55.765835f, "apac"), - new("UZ", "Uzbekistan", 41.377491f, 64.585262f, "apac"), - new("VA", "Vatican City", 41.902916f, 12.453389f, "weur"), - new("VC", "Saint Vincent and the Grenadines", 12.984305f, -61.287228f, "apac"), - new("VE", "Venezuela", 6.42375f, -66.58973f, "apac"), - new("VG", "British Virgin Islands", 18.420695f, -64.639968f, "apac"), - new("VI", "U.S. Virgin Islands", 18.335765f, -64.896335f, "apac"), - new("VN", "Vietnam", 14.058324f, 108.277199f, "apac"), - new("VU", "Vanuatu", -15.376706f, 166.959158f, "apac"), - new("WF", "Wallis and Futuna", -13.768752f, -177.156097f, "apac"), - new("WS", "Samoa", -13.759029f, -172.104629f, "apac"), - new("XK", "Kosovo", 42.602636f, 20.902977f, null), - new("YE", "Yemen", 15.552727f, 48.516388f, "apac"), - new("YT", "Mayotte", -12.8275f, 45.166244f, "apac"), - new("ZA", "South Africa", -30.559482f, 22.937506f, "apac"), - new("ZM", "Zambia", -13.133897f, 27.849332f, "apac"), - new("ZW", "Zimbabwe", -19.015438f, 29.154857f, "apac"), - ]; - - public static readonly FrozenDictionary CodeDictionary = Countries.ToFrozenDictionary(x => x.CountryCode, x => x); // Create a frozen dictionary for fast lookups -} diff --git a/Common/Geo/DistanceLookup.cs b/Common/Geo/DistanceLookup.cs deleted file mode 100644 index ae72c5b1..00000000 --- a/Common/Geo/DistanceLookup.cs +++ /dev/null @@ -1,59 +0,0 @@ -using OpenShock.Common.Utils; -using System.Collections.Frozen; -using OpenShock.Common.Constants; - -namespace OpenShock.Common.Geo; - -public static class DistanceLookup -{ - /// - /// Generates all distances between all countries in the world, along with their unique ID - /// - /// Generates approximately 22k entries - /// - /// - private static IEnumerable> GetAllDistances() - { - for (int i = 0; i < CountryInfo.Countries.Length; i++) - { - var first = CountryInfo.Countries[i]; - - // Same country, no need to calculate distance - yield return new KeyValuePair(Alpha2CountryCode.GetCombinedHashCode(first.CountryCode, first.CountryCode), 0f); - - for (int j = i + 1; j < CountryInfo.Countries.Length; j++) - { - var second = CountryInfo.Countries[j]; - - var id = Alpha2CountryCode.GetCombinedHashCode(first.CountryCode, second.CountryCode); - var dist = MathUtils.CalculateHaversineDistance(first.Latitude, first.Longitude, second.Latitude, second.Longitude); - - yield return new KeyValuePair(id, dist); - } - } - } - - /// - /// Stupidly fast lookup for distances between countries - /// - private static readonly FrozenDictionary Distances = GetAllDistances().ToFrozenDictionary(); // Create a frozen dictionary for fast lookups - - public static bool TryGetDistanceBetween(Alpha2CountryCode alpha2CountryA, Alpha2CountryCode alpha2CountryB, out float distance) - { - if (alpha2CountryA.IsUnknown() || alpha2CountryB.IsUnknown()) - { - distance = Distance.DistanceToAndromedaGalaxyInKm; - - return false; - } - - if (!Distances.TryGetValue(Alpha2CountryCode.GetCombinedHashCode(alpha2CountryA, alpha2CountryB), out distance)) - { - distance = Distance.DistanceToAndromedaGalaxyInKm; - - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/Common/Hubs/PublicShareHub.cs b/Common/Hubs/PublicShareHub.cs index 15fcf4f4..a087f5e7 100644 --- a/Common/Hubs/PublicShareHub.cs +++ b/Common/Hubs/PublicShareHub.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Services.Session; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.Hubs; public sealed class PublicShareHub : Hub diff --git a/Common/JsonSerialization/FlagGuardedJsonStringEnumConverter.cs b/Common/JsonSerialization/FlagGuardedJsonStringEnumConverter.cs deleted file mode 100644 index b49d4d11..00000000 --- a/Common/JsonSerialization/FlagGuardedJsonStringEnumConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace OpenShock.Common.JsonSerialization; - -public sealed class FlagGuardedJsonStringEnumConverter : JsonConverterFactory -{ - private static readonly JsonStringEnumConverter JsonStringEnumConverter = new(); - - public override bool CanConvert(Type typeToConvert) => - !typeToConvert.IsDefined(typeof(FlagsAttribute), false) && - JsonStringEnumConverter.CanConvert(typeToConvert); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => - JsonStringEnumConverter.CreateConverter(typeToConvert, options); -} \ No newline at end of file diff --git a/Common/JsonSerialization/JsonOptions.cs b/Common/JsonSerialization/JsonOptions.cs index 45e21a85..6fa0fd94 100644 --- a/Common/JsonSerialization/JsonOptions.cs +++ b/Common/JsonSerialization/JsonOptions.cs @@ -1,5 +1,7 @@ using System.Text.Json; +using OpenShock.Internal.Common.JsonSerialization; + namespace OpenShock.Common.JsonSerialization; public static class JsonOptions diff --git a/Common/JsonSerialization/UnixMillisecondsDateTimeOffsetConverter.cs b/Common/JsonSerialization/UnixMillisecondsDateTimeOffsetConverter.cs deleted file mode 100644 index d37e943a..00000000 --- a/Common/JsonSerialization/UnixMillisecondsDateTimeOffsetConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace OpenShock.Common.JsonSerialization; - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -public class UnixMillisecondsDateTimeOffsetConverter : JsonConverter -{ - // Serialize DateTimeOffset to Unix time in milliseconds - public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) - { - var unixTimeMilliseconds = value.ToUnixTimeMilliseconds(); - writer.WriteNumberValue(unixTimeMilliseconds); - } - - // Deserialize Unix time in milliseconds to DateTimeOffset - public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.Number) - { - throw new JsonException("Expected number token for Unix time in milliseconds."); - } - - var unixTimeMilliseconds = reader.GetInt64(); - return DateTimeOffset.FromUnixTimeMilliseconds(unixTimeMilliseconds); - } -} diff --git a/Common/Models/ApiTokenControlLimits.cs b/Common/Models/ApiTokenControlLimits.cs index 9d7dbe15..90ecafe1 100644 --- a/Common/Models/ApiTokenControlLimits.cs +++ b/Common/Models/ApiTokenControlLimits.cs @@ -1,6 +1,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.OpenShockDb; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.Models; /// diff --git a/Common/Models/PasswordHashingAlgorithm.cs b/Common/Models/PasswordHashingAlgorithm.cs deleted file mode 100644 index 4b19f361..00000000 --- a/Common/Models/PasswordHashingAlgorithm.cs +++ /dev/null @@ -1,9 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace OpenShock.Common.Models; - -public enum PasswordHashingAlgorithm -{ - Unknown = -1, - BCrypt = 0, - PBKDF2 = 1, -}; \ No newline at end of file diff --git a/Common/Models/WebSocket/User/Control.cs b/Common/Models/WebSocket/User/Control.cs index 9400133c..bc7b742b 100644 --- a/Common/Models/WebSocket/User/Control.cs +++ b/Common/Models/WebSocket/User/Control.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.Models.WebSocket.User; // ReSharper disable once ClassNeverInstantiated.Global diff --git a/Common/Models/WebSocket/User/ControlLog.cs b/Common/Models/WebSocket/User/ControlLog.cs index 424f9adc..c392837d 100644 --- a/Common/Models/WebSocket/User/ControlLog.cs +++ b/Common/Models/WebSocket/User/ControlLog.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.Models.WebSocket.User; // ReSharper disable once ClassNeverInstantiated.Global diff --git a/Common/OpenShockControllerBase.cs b/Common/OpenShockControllerBase.cs index 4e11c2cb..2e0a5dd5 100644 --- a/Common/OpenShockControllerBase.cs +++ b/Common/OpenShockControllerBase.cs @@ -2,17 +2,17 @@ using OpenShock.Common.Constants; using OpenShock.Common.Models; using OpenShock.Common.Options; -using OpenShock.Common.Problems; using OpenShock.Common.Services.Session; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common; -public class OpenShockControllerBase : ControllerBase +// Inherits Problem(OpenShockProblem) from OpenShock.Internal.Common.OpenShockControllerBase; +// the members below are OpenShock-API-specific and stay local. +public class OpenShockControllerBase : OpenShock.Internal.Common.OpenShockControllerBase { - [NonAction] - protected ObjectResult Problem(OpenShockProblem problem) => problem.ToObjectResult(HttpContext); - [NonAction] protected OkObjectResult LegacyDataOk(T data, string message = "") { diff --git a/Common/OpenShockDb/AdminUsersView.cs b/Common/OpenShockDb/AdminUsersView.cs index 536a57ec..457b8f91 100644 --- a/Common/OpenShockDb/AdminUsersView.cs +++ b/Common/OpenShockDb/AdminUsersView.cs @@ -2,6 +2,8 @@ // We are in a view, no need to restrict lengths lol // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength +using OpenShock.Internal.Common.Enums; + namespace OpenShock.Common.OpenShockDb; public sealed class AdminUsersView diff --git a/Common/OpenShockDb/ApiToken.cs b/Common/OpenShockDb/ApiToken.cs index b1e3b139..471a83c8 100644 --- a/Common/OpenShockDb/ApiToken.cs +++ b/Common/OpenShockDb/ApiToken.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.OpenShockDb; public sealed class ApiToken diff --git a/Common/OpenShockDb/OpenShockContext.cs b/Common/OpenShockDb/OpenShockContext.cs index 66de70f0..9f9f3473 100644 --- a/Common/OpenShockDb/OpenShockContext.cs +++ b/Common/OpenShockDb/OpenShockContext.cs @@ -4,6 +4,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.OpenShockDb; /// @@ -174,7 +176,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.LastUsed) .HasColumnName("last_used"); entity.Property(e => e.Name) - .HasMaxLength(HardLimits.ApiKeyNameMaxLength) + .HasMaxLength(ApiHardLimits.ApiKeyNameMaxLength) .HasColumnName("name"); entity.Property(e => e.TokenHash) .UseCollation("C") @@ -274,7 +276,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.OwnerId).HasColumnName("owner_id"); entity.Property(e => e.Token) .UseCollation("C") - .HasMaxLength(HardLimits.HubTokenMaxLength) + .HasMaxLength(ApiHardLimits.HubTokenMaxLength) .HasColumnName("token"); entity.HasOne(d => d.Owner).WithMany(p => p.Devices) @@ -296,7 +298,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasDefaultValueSql("CURRENT_TIMESTAMP") .HasColumnName("created_at"); entity.Property(e => e.Message) - .VarCharWithLength(HardLimits.OtaUpdateMessageMaxLength) + .VarCharWithLength(ApiHardLimits.OtaUpdateMessageMaxLength) .HasColumnName("message"); entity.Property(e => e.Version) .VarCharWithLength(HardLimits.SemVerMaxLength) @@ -326,7 +328,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("created_at"); entity.Property(e => e.TokenHash) .UseCollation("C") - .VarCharWithLength(HardLimits.PasswordResetSecretMaxLength) + .VarCharWithLength(ApiHardLimits.PasswordResetSecretMaxLength) .HasColumnName("token_hash"); entity.Property(e => e.SecurityStampAtCreate) .HasColumnName("security_stamp_at_create"); @@ -631,7 +633,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("email"); entity.Property(e => e.PasswordHash) .UseCollation("C") - .VarCharWithLength(HardLimits.PasswordHashMaxLength) + .VarCharWithLength(ApiHardLimits.PasswordHashMaxLength) .HasColumnName("password_hash"); entity.Property(e => e.SecurityStamp) .HasDefaultValueSql("gen_random_uuid()") @@ -684,7 +686,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("user_id"); entity.Property(e => e.TokenHash) .UseCollation("C") - .VarCharWithLength(HardLimits.UserActivationRequestSecretMaxLength) + .VarCharWithLength(ApiHardLimits.UserActivationRequestSecretMaxLength) .HasColumnName("token_hash"); entity.Property(e => e.EmailSendAttempts) .HasColumnName("email_send_attempts"); @@ -747,7 +749,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("email_new"); entity.Property(e => e.TokenHash) .UseCollation("C") - .VarCharWithLength(HardLimits.UserEmailChangeSecretMaxLength) + .VarCharWithLength(ApiHardLimits.UserEmailChangeSecretMaxLength) .HasColumnName("token_hash"); entity.Property(e => e.SecurityStampAtCreate) .HasColumnName("security_stamp_at_create"); @@ -865,7 +867,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnName("id"); entity.Property(e => e.Domain) .UseCollation("ndcoll") - .VarCharWithLength(HardLimits.EmailProviderDomainMaxLength) + .VarCharWithLength(ApiHardLimits.EmailProviderDomainMaxLength) .HasColumnName("domain"); entity.Property(e => e.CreatedAt) .HasDefaultValueSql("CURRENT_TIMESTAMP") diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 038304e0..33f0371d 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -10,6 +10,8 @@ using Serilog; using IPNetwork = System.Net.IPNetwork; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common; public static class OpenShockMiddlewareHelper diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index 232a2202..53706dc0 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -11,7 +11,7 @@ using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.AuthenticationHandlers; using OpenShock.Common.Authentication.Services; -using OpenShock.Common.ExceptionHandle; +using OpenShock.Internal.Common.ExceptionHandling; using OpenShock.Common.JsonSerialization; using OpenShock.Common.OpenShockDb; using OpenShock.Common.Options; @@ -100,6 +100,8 @@ public static IServiceCollection AddOpenShockServices(this IServiceCollection se Action? configureAuth = null, Action? configureMetrics = null) { // <---- ASP.NET ----> + // OpenShockExceptionHandler (OpenShock.Internal.AspNet) takes JsonSerializerOptions via ctor injection. + services.AddSingleton(JsonOptions.Default); services.AddExceptionHandler(); services.AddHybridCache(options => diff --git a/Common/Problems/CustomProblems/PolicyNotMetProblem.cs b/Common/Problems/CustomProblems/PolicyNotMetProblem.cs index 9345c644..47c53ea5 100644 --- a/Common/Problems/CustomProblems/PolicyNotMetProblem.cs +++ b/Common/Problems/CustomProblems/PolicyNotMetProblem.cs @@ -1,5 +1,7 @@ using System.Net; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Problems.CustomProblems; public class PolicyNotMetProblem : OpenShockProblem diff --git a/Common/Problems/CustomProblems/ShockerControlProblem.cs b/Common/Problems/CustomProblems/ShockerControlProblem.cs index 237077a6..c35f471a 100644 --- a/Common/Problems/CustomProblems/ShockerControlProblem.cs +++ b/Common/Problems/CustomProblems/ShockerControlProblem.cs @@ -1,5 +1,7 @@ using System.Net; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Problems.CustomProblems; public sealed class ShockerControlProblem( diff --git a/Common/Problems/CustomProblems/ShockersNotFoundProblem.cs b/Common/Problems/CustomProblems/ShockersNotFoundProblem.cs index 29ed27fd..51636bc3 100644 --- a/Common/Problems/CustomProblems/ShockersNotFoundProblem.cs +++ b/Common/Problems/CustomProblems/ShockersNotFoundProblem.cs @@ -1,5 +1,7 @@ using System.Net; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Problems.CustomProblems; public sealed class ShockersNotFoundProblem( diff --git a/Common/Problems/CustomProblems/TokenPermissionProblem.cs b/Common/Problems/CustomProblems/TokenPermissionProblem.cs index d23b0598..de63feb5 100644 --- a/Common/Problems/CustomProblems/TokenPermissionProblem.cs +++ b/Common/Problems/CustomProblems/TokenPermissionProblem.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Problems.CustomProblems; public sealed class TokenPermissionProblem( diff --git a/Common/Problems/ExceptionProblem.cs b/Common/Problems/ExceptionProblem.cs deleted file mode 100644 index a4c56ada..00000000 --- a/Common/Problems/ExceptionProblem.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Net; - -namespace OpenShock.Common.Problems; - -public sealed class ExceptionProblem : OpenShockProblem -{ - public ExceptionProblem() : base("Exception", "An unknown exception occurred", HttpStatusCode.InternalServerError, "An unknown error occurred. Please try again later. If the issue persists reach out to support.") - { - } -} \ No newline at end of file diff --git a/Common/Problems/OpenShockProblem.cs b/Common/Problems/OpenShockProblem.cs deleted file mode 100644 index 123f890a..00000000 --- a/Common/Problems/OpenShockProblem.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Net; -using System.Net.Mime; -using Microsoft.AspNetCore.Mvc; -using JsonOptions = OpenShock.Common.JsonSerialization.JsonOptions; - -namespace OpenShock.Common.Problems; - -/// -/// Represents a problem -/// -public class OpenShockProblem : ProblemDetails -{ - public OpenShockProblem(string type, string title, HttpStatusCode status, - string? detail = null) : this(type, title, (int)status, detail) - { - } - - private OpenShockProblem(string type, string title, int status = 400, string? detail = null) - { - Type = type; - Title = title; - Detail = detail; - Status = status; - } - - [Obsolete("This is the exact same as title or detail if present, refer to using title in the future")] - public string Message => Detail ?? Title!; - - [Obsolete("This is the exact same as requestId, refer to using requestId in the future")] - public string? TraceId => RequestId; - - public string? RequestId { get; private set; } - - public ObjectResult ToObjectResult(HttpContext context) - { - RequestId = context.TraceIdentifier; - - return new ObjectResult(this) - { - StatusCode = Status - }; - } - - public Task WriteAsJsonAsync(HttpContext context, CancellationToken cancellationToken) - { - context.Response.StatusCode = Status ?? StatusCodes.Status400BadRequest; - RequestId = context.TraceIdentifier; - - return context.Response.WriteAsJsonAsync(this, JsonOptions.Default, MediaTypeNames.Application.ProblemJson, cancellationToken); - } - - public Task WriteAsJsonAsync(HttpContext context) => WriteAsJsonAsync(context, context.RequestAborted); -} \ No newline at end of file diff --git a/Common/Problems/ValidationProblem.cs b/Common/Problems/ValidationProblem.cs index 1063cb26..302e88b6 100644 --- a/Common/Problems/ValidationProblem.cs +++ b/Common/Problems/ValidationProblem.cs @@ -1,6 +1,8 @@ using System.Net; using Microsoft.AspNetCore.Mvc.ModelBinding; +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Problems; public sealed class ValidationProblem : OpenShockProblem diff --git a/Common/Query/DBExpressionBuilder.cs b/Common/Query/DBExpressionBuilder.cs deleted file mode 100644 index 377a582d..00000000 --- a/Common/Query/DBExpressionBuilder.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Linq.Expressions; -using System.Text.RegularExpressions; - -namespace OpenShock.Common.Query; - -public sealed class DBExpressionBuilderException : Exception -{ - public DBExpressionBuilderException(string message) : base(message) { } -} - -public static partial class DBExpressionBuilder -{ - [GeneratedRegex(@"^[A-Za-z][A-Za-z0-9]*$")] - private static partial Regex ValidMemberNameRegex(); - - private static Expression CreateMemberCompareExpression(Type entityType, ParameterExpression parameterExpr, string propOrFieldName, string operation, string value) - { - var (memberInfo, memberType) = DBExpressionBuilderUtils.GetPropertyOrField(entityType, propOrFieldName); - - var memberExpr = Expression.MakeMemberAccess(parameterExpr, memberInfo); - - // ReSharper disable once StringLiteralTypo - Expression? resultExpr = operation switch - { - //"like" => DBExpressionBuilderUtils.BuildEfFunctionsLikeExpression(memberType, memberExpr, value), - "ilike" => DBExpressionBuilderUtils.BuildEfFunctionsCollatedILikeExpression(memberType, memberExpr, value), - "==" or "eq" => DBExpressionBuilderUtils.BuildEqualExpression(memberType, memberExpr, value), - "!=" or "neq" => DBExpressionBuilderUtils.BuildNotEqualExpression(memberType, memberExpr, value), - "<" or "lt" => DBExpressionBuilderUtils.BuildLessThanExpression(memberType, memberExpr, value), - ">" or "gt" => DBExpressionBuilderUtils.BuildGreaterThanExpression(memberType, memberExpr, value), - "<=" or "lte" => DBExpressionBuilderUtils.BuildLessThanOrEqualExpression(memberType, memberExpr, value), - ">=" or "gte" => DBExpressionBuilderUtils.BuildGreaterThanOrEqualExpression(memberType, memberExpr, value), - _ => throw new DBExpressionBuilderException($"'{operation}' is not a supported operation type.") - }; - - return resultExpr ?? throw new DBExpressionBuilderException($"Operation {operation} is not supported for {memberType}"); - } - - - private sealed record ParsedFilter(string MemberName, string Operation, string Value); - private enum ExpectedToken - { - Member, - Operation, - Value, - AndOrEnd - } - private static IEnumerable ParseFilters(string query) - { - var member = string.Empty; - var operation = string.Empty; - var expectedToken = ExpectedToken.Member; - foreach (var word in QueryStringTokenizer.ParseQueryTokens(query)) - { - switch (expectedToken) - { - case ExpectedToken.Member: - member = word; - expectedToken = ExpectedToken.Operation; - break; - case ExpectedToken.Operation: - operation = word; - expectedToken = ExpectedToken.Value; - break; - case ExpectedToken.Value: - if (!ValidMemberNameRegex().IsMatch(member)) - throw new DBExpressionBuilderException("Invalid filter string!"); - - if (string.IsNullOrEmpty(operation)) - throw new DBExpressionBuilderException("Invalid filter string!"); - - yield return new ParsedFilter(member, operation, word); - - member = string.Empty; - operation = string.Empty; - expectedToken = ExpectedToken.AndOrEnd; - break; - case ExpectedToken.AndOrEnd: - if (word != "and") throw new DBExpressionBuilderException("Only and is supported atm!"); - expectedToken = ExpectedToken.Member; - break; - default: - throw new DBExpressionBuilderException("Unexpected state!"); - } - } - - if (expectedToken != ExpectedToken.AndOrEnd) - throw new DBExpressionBuilderException("Unexpected end of query"); - } - - public static Expression> GetFilterExpression(string filterQuery) where T : class - { - var entityType = typeof(T); - var parameterExpr = Expression.Parameter(entityType, "x"); - - var completeExpr = ParseFilters(filterQuery) - .Select(filter => CreateMemberCompareExpression(entityType, parameterExpr, filter.MemberName, filter.Operation, filter.Value)) - .Aggregate(null, (prev, next) => prev is null ? next : Expression.And(prev, next)); - - return Expression.Lambda>(completeExpr ?? Expression.Constant(true), parameterExpr); - } -} diff --git a/Common/Query/DBExpressionBuilderUtils.cs b/Common/Query/DBExpressionBuilderUtils.cs deleted file mode 100644 index ac63f4a7..00000000 --- a/Common/Query/DBExpressionBuilderUtils.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.Serialization; - -namespace OpenShock.Common.Query; - -public static class DBExpressionBuilderUtils -{ - private static readonly MethodInfo EfFunctionsCollateMethodInfo = typeof(RelationalDbFunctionsExtensions).GetMethod("Collate")?.MakeGenericMethod(typeof(string)) ?? throw new MissingMethodException("EF.Functions", "Collate(string,string)"); - private static readonly MethodInfo EfFunctionsILikeMethodInfo = typeof(NpgsqlDbFunctionsExtensions).GetMethod("ILike", [typeof(DbFunctions), typeof(string), typeof(string)]) ?? throw new MissingMethodException("EF.Functions", "ILike(string,string)"); - private static readonly MethodInfo StringEqualsMethodInfo = typeof(string).GetMethod("Equals", [typeof(string)]) ?? throw new MissingMethodException("string", "Equals(string,StringComparison)"); - private static readonly MethodInfo StringStartsWithMethodInfo = typeof(string).GetMethod("StartsWith", [typeof(string)]) ?? throw new MissingMethodException("string", "StartsWith(string)"); - private static readonly MethodInfo StringEndsWithMethodInfo = typeof(string).GetMethod("EndsWith", [typeof(string)]) ?? throw new MissingMethodException("string", "EndsWith(string)"); - private static readonly MethodInfo StringContainsMethodInfo = typeof(string).GetMethod("Contains", [typeof(string)]) ?? throw new MissingMethodException("string","Contains(string)"); - - /// - /// To not let whoever's requesting to explore hidden data structures, we return same exception for all errors here - /// - /// - /// - /// - /// - public static (MemberInfo, Type) GetPropertyOrField(Type type, string propOrFieldName) - { - var memberInfo = type.GetMember(propOrFieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.IgnoreCase).SingleOrDefault(); - if (memberInfo is null) - throw new DBExpressionBuilderException($"'{propOrFieldName}' is not a valid property of type {type.Name}"); - - var isIgnored = memberInfo.GetCustomAttributes(typeof(IgnoreDataMemberAttribute), true).Any(); - if (isIgnored) - throw new DBExpressionBuilderException($"'{propOrFieldName}' is not a valid property of type {type.Name}"); - - var memberType = memberInfo switch - { - PropertyInfo prop => prop.PropertyType, - FieldInfo field => field.FieldType, - _ => throw new DBExpressionBuilderException($"'{propOrFieldName}' is not a valid property of type {type.Name}") - }; - - return (memberInfo, memberType); - } - - public static LambdaExpression CreatePropertyOrFieldAccessorExpression(string propOrFieldName) - { - var parameterExpr = Expression.Parameter(typeof(T), "x"); - - var memberExpr = Expression.PropertyOrField(parameterExpr, propOrFieldName); - - return Expression.Lambda(memberExpr, parameterExpr); - } - - private static ConstantExpression GetConstant(Type type, string value) - { - // Handling of nullable types - if (Nullable.GetUnderlyingType(type) is { } underlyingType) - { - if (string.IsNullOrWhiteSpace(value)) - return Expression.Constant(null, type); // null literal - - return GetConstant(underlyingType, value); // recurse with unwrapped - } - - if (type.IsEnum) - { - if (string.IsNullOrWhiteSpace(value)) - { - throw new FormatException($"'{type.Name}' cannot be null or empty"); - } - - object enumValue; - try - { - enumValue = Enum.Parse(type, value, ignoreCase: true); - } - catch (ArgumentException e) - { - throw new FormatException($"'{value}' is not a valid value for type {type.Name}", e); - } - return Expression.Constant(enumValue, type); - } - - // ReSharper disable BuiltInTypeReferenceStyleForMemberAccess - return Expression.Constant(Type.GetTypeCode(type) switch - { - TypeCode.Empty => throw new NotImplementedException(), - TypeCode.Object => HandleObject(type, value), - TypeCode.DBNull => throw new NotImplementedException(), - TypeCode.Boolean => Boolean.Parse(value), - TypeCode.Char => Char.Parse(value), - TypeCode.SByte => SByte.Parse(value), - TypeCode.Byte => Byte.Parse(value), - TypeCode.Int16 => Int16.Parse(value), - TypeCode.UInt16 => UInt16.Parse(value), - TypeCode.Int32 => Int32.Parse(value), - TypeCode.UInt32 => UInt32.Parse(value), - TypeCode.Int64 => Int64.Parse(value), - TypeCode.UInt64 => UInt64.Parse(value), - TypeCode.Single => Single.Parse(value), - TypeCode.Double => Double.Parse(value), - TypeCode.Decimal => Decimal.Parse(value), - TypeCode.DateTime => DateTime.Parse(value).ToUniversalTime(), - TypeCode.String => value, - _ => HandleUnknown(type, value), - }); - - static object? HandleObject(Type type, string value) - { - if (type == typeof(Guid)) - { - return Guid.Parse(value); - } - - throw new NotImplementedException(); - } - - // ReSharper restore BuiltInTypeReferenceStyleForMemberAccess - - static object? HandleUnknown(Type type, string value) - { - - throw new NotImplementedException(); - } - } - - public static MethodCallExpression? BuildEfFunctionsCollatedILikeExpression(Type memberType, Expression memberExpr, string value) - { - if (memberType != typeof(string)) return null; - - var valueConstant = Expression.Constant(value, typeof(string)); - var defaultStrConstant = Expression.Constant("default", typeof(string)); - var efFunctionsConstant = Expression.Constant(EF.Functions, typeof(DbFunctions)); - - var collated = Expression.Call(null, EfFunctionsCollateMethodInfo, efFunctionsConstant, memberExpr, defaultStrConstant); - - return Expression.Call(null, EfFunctionsILikeMethodInfo, efFunctionsConstant, collated, valueConstant); - } - - public static BinaryExpression BuildEqualExpression(Type memberType, Expression memberExpr, string value) - { - return Expression.Equal(memberExpr, GetConstant(memberType, value)); - } - - public static BinaryExpression BuildNotEqualExpression(Type memberType, Expression memberExpr, string value) - { - return Expression.NotEqual(memberExpr, GetConstant(memberType, value)); - } - - public static BinaryExpression? BuildLessThanExpression(Type memberType, Expression memberExpr, string value) - { - if (memberType is { IsPrimitive: false, IsEnum: false } && Type.GetTypeCode(memberType) != TypeCode.DateTime) return null; - return Expression.LessThan(memberExpr, GetConstant(memberType, value)); - } - - public static BinaryExpression? BuildGreaterThanExpression(Type memberType, Expression memberExpr, string value) - { - if (memberType is { IsPrimitive: false, IsEnum: false } && Type.GetTypeCode(memberType) != TypeCode.DateTime) return null; - return Expression.GreaterThan(memberExpr, GetConstant(memberType, value)); - } - - public static BinaryExpression? BuildLessThanOrEqualExpression(Type memberType, Expression memberExpr, string value) - { - if (memberType is { IsPrimitive: false, IsEnum: false } && Type.GetTypeCode(memberType) != TypeCode.DateTime) return null; - return Expression.LessThanOrEqual(memberExpr, GetConstant(memberType, value)); - } - - public static BinaryExpression? BuildGreaterThanOrEqualExpression(Type memberType, Expression memberExpr, string value) - { - if (memberType is { IsPrimitive: false, IsEnum: false } && Type.GetTypeCode(memberType) != TypeCode.DateTime) return null; - return Expression.GreaterThanOrEqual(memberExpr, GetConstant(memberType, value)); - } -} diff --git a/Common/Query/OrderByQueryBuilder.cs b/Common/Query/OrderByQueryBuilder.cs deleted file mode 100644 index f41a92e8..00000000 --- a/Common/Query/OrderByQueryBuilder.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace OpenShock.Common.Query; - -public static class OrderByQueryBuilder -{ - private static MethodInfo[] PublicQueryableMethods => typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public); - private static readonly MethodInfo OrderByAscendingMethodInfo = PublicQueryableMethods.Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2); - private static readonly MethodInfo OrderByDescendingMethodInfo = PublicQueryableMethods.Single(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2); - private static readonly MethodInfo OrderThenByAscendingMethodInfo = PublicQueryableMethods.Single(m => m.Name == "ThenBy" && m.GetParameters().Length == 2); - private static readonly MethodInfo OrderThenByDescendingMethodInfo = PublicQueryableMethods.Single(m => m.Name == "ThenByDescending" && m.GetParameters().Length == 2); - - private record struct OrderByItem(string Name, bool Descending); - - private static OrderByItem ParseOrderByPart(string str) - { - var parts = str.Split(' '); - if (parts.Length == 1) - { - return new OrderByItem(str, false); - } - - if (parts.Length == 2) - { - bool descending = parts[1].ToLower() switch - { - "asc" => false, - "desc" => true, - _ => throw new InvalidOperationException("Direction if specified must be 'asc' or 'desc'."), - }; - - return new OrderByItem(parts[0], descending); - } - - throw new InvalidOperationException("Invalid orderby query."); - } - - private static IOrderedQueryable OrderBy(this IQueryable source, LambdaExpression keySelector, bool descending = false) - { - var method = (descending ? OrderByDescendingMethodInfo : OrderByAscendingMethodInfo) - .MakeGenericMethod(typeof(T), keySelector.Body.Type); - - var call = Expression.Call(null, method, source.Expression, Expression.Quote(keySelector)); - - return (IOrderedQueryable)source.Provider.CreateQuery(call); - } - - private static IOrderedQueryable ThenBy(this IOrderedQueryable source, LambdaExpression keySelector, bool descending = false) - { - var method = (descending ? OrderThenByDescendingMethodInfo : OrderThenByAscendingMethodInfo) - .MakeGenericMethod(typeof(T), keySelector.Body.Type); - - var call = Expression.Call(null, method, source.Expression, Expression.Quote(keySelector)); - - return (IOrderedQueryable)source.Provider.CreateQuery(call); - } - - public static IOrderedQueryable ApplyOrderBy(this IQueryable query, string orderbyQuery) where T : class - { - ArgumentNullException.ThrowIfNull(query); - ArgumentException.ThrowIfNullOrWhiteSpace(orderbyQuery); - - var parts = orderbyQuery.Split(','); - - var parsed = ParseOrderByPart(parts[0]); - var orderedQuery = query.OrderBy(DBExpressionBuilderUtils.CreatePropertyOrFieldAccessorExpression(parsed.Name), parsed.Descending); - - for (int i = 1; i < parts.Length; ++i) - { - parsed = ParseOrderByPart(parts[i]); - orderedQuery = orderedQuery.ThenBy(DBExpressionBuilderUtils.CreatePropertyOrFieldAccessorExpression(parsed.Name), parsed.Descending); - } - - return orderedQuery; - } -} diff --git a/Common/Query/QueryStringTokenizer.cs b/Common/Query/QueryStringTokenizer.cs deleted file mode 100644 index 0e55b8a2..00000000 --- a/Common/Query/QueryStringTokenizer.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Buffers; -using System.Text; - -namespace OpenShock.Common.Query; - -public sealed class QueryStringTokenizerException : Exception -{ - public QueryStringTokenizerException(string message) : base(message) { } -} - -public static class QueryStringTokenizer -{ - private const char QueryQuoteChar = '\''; - private const char QueryEscapeChar = '\\'; - - // In unquoted strings, search for quotes and escapes. If these are found we should fail the parsing. - private static readonly SearchValues UnquotedSearchValues = SearchValues.Create(' ', '\r', '\n', '\t', QueryQuoteChar, QueryEscapeChar); - - /// - /// Parses a query string into a list of words, handling spaces, quoted strings, and escape sequences. - /// - /// The input query as a readonly char span. - /// A list of parsed words from the query. - /// - /// Thrown when the query contains an invalid escape sequence, an unclosed quoted string, or other syntax errors. - /// - /// - /// - /// var result = ParseQueryWords("hello world"); - /// result will contain: ["hello", "world"] - /// - /// var result = ParseQueryWords("'hello world'"); - /// result will contain: ["hello world"] - /// - /// var result = ParseQueryWords("this 'isn\'t invalid'"); - /// result will contain: ["this", "isn't invalid"] - /// - /// - public static List ParseQueryTokens(ReadOnlySpan query) - { - query = query.Trim(); - - List tokens = []; - - while (!query.IsEmpty) - { - int i; - if (query[0] != QueryQuoteChar) - { - i = query.IndexOfAny(UnquotedSearchValues); - if (i < 0) - { - // End of query - tokens.Add(query.ToString()); - break; - } - - // Error on non-whitespace syntax character - if (!char.IsWhiteSpace(query[i])) - throw new QueryStringTokenizerException("Invalid unquoted string in query."); - - // Next space seperated part - tokens.Add(query[..i].ToString()); - - query = query[(i + 1)..].TrimStart(); - continue; - } - - // Skip quote char - query = query[1..]; - - // Find next quote or escape char - i = query.IndexOfAny(QueryQuoteChar, QueryEscapeChar); - if (i < 0) - throw new QueryStringTokenizerException("Closing quote not found."); - - // Fast path: string contains no escapes - if (query[i] == QueryQuoteChar) - { - // If i is 1 then its empty quotes - tokens.Add(i == 0 ? string.Empty : query[..i].ToString()); - query = query[(i + 1)..].TrimStart(); - continue; - } - - var sb = new StringBuilder(); - - // Parse escaped string - while (true) - { - // Add everything before escape - if (i > 0) sb.Append(query[..i]); - - // Needs space for escape sequence and end of string - if (i + 2 >= query.Length) - throw new QueryStringTokenizerException("Invalid end of query."); - - // Add escape - sb.Append(query[i + 1] switch - { - QueryQuoteChar => QueryQuoteChar, - QueryEscapeChar => QueryEscapeChar, - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - _ => throw new QueryStringTokenizerException("Invalid escape sequence.") - }); - - // Skip past escape sequence - query = query[(i + 2)..]; - - i = query.IndexOfAny(QueryQuoteChar, QueryEscapeChar); - if (i < 0) - throw new QueryStringTokenizerException("Closing quote not found."); - - if (query[i] == QueryQuoteChar) - { - // Add everything before quote - if (i > 0) sb.Append(query[..i]); - - // Finish off string - tokens.Add(sb.ToString()); - - query = query[(i + 1)..].TrimStart(); - break; - } - - // Loop continues at escape found - } - } - - return tokens; - } -} diff --git a/Common/Redis/LoginSessions.cs b/Common/Redis/LoginSessions.cs index 34b4f8a3..034e6e90 100644 --- a/Common/Redis/LoginSessions.cs +++ b/Common/Redis/LoginSessions.cs @@ -2,6 +2,8 @@ using OpenShock.Common.JsonSerialization; using Redis.OM.Modeling; +using OpenShock.Internal.Common.JsonSerialization; + namespace OpenShock.Common.Redis; [Document(StorageType = StorageType.Json, IndexName = "login-session-v2")] diff --git a/Common/Redis/QueueHelper.cs b/Common/Redis/QueueHelper.cs index 29c8f0bc..723e2564 100644 --- a/Common/Redis/QueueHelper.cs +++ b/Common/Redis/QueueHelper.cs @@ -1,6 +1,8 @@ using OpenShock.Common.Utils; using StackExchange.Redis; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Redis; public static class QueueHelper diff --git a/Common/Services/BatchUpdate/BatchUpdateService.cs b/Common/Services/BatchUpdate/BatchUpdateService.cs index 9dcfd26b..6c62ddc7 100644 --- a/Common/Services/BatchUpdate/BatchUpdateService.cs +++ b/Common/Services/BatchUpdate/BatchUpdateService.cs @@ -8,6 +8,8 @@ using StackExchange.Redis; using Timer = System.Timers.Timer; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Services.BatchUpdate; internal sealed class ConcurrentUniqueBatchQueue where TKey : notnull diff --git a/Common/Services/ControlSender.cs b/Common/Services/ControlSender.cs index 92698913..e73d6a7f 100644 --- a/Common/Services/ControlSender.cs +++ b/Common/Services/ControlSender.cs @@ -13,6 +13,10 @@ using OpenShock.Common.Services.RedisPubSub; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Extensions; + namespace OpenShock.Common.Services; public sealed class ControlSender : IControlSender diff --git a/Common/Services/Session/SessionService.cs b/Common/Services/Session/SessionService.cs index f506640f..949d8f2c 100644 --- a/Common/Services/Session/SessionService.cs +++ b/Common/Services/Session/SessionService.cs @@ -6,6 +6,8 @@ using Redis.OM.Contracts; using Redis.OM.Searching; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Services.Session; /// @@ -27,7 +29,7 @@ public SessionService(IRedisConnectionProvider redisConnectionProvider) public async Task CreateSessionAsync(Guid userId, string userAgent, string ipAddress) { Guid id = Guid.CreateVersion7(); - string token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + string token = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); await _loginSessions.InsertAsync(new LoginSession { diff --git a/Common/Utils/CloudflareNetworks.g.cs b/Common/Utils/CloudflareNetworks.g.cs deleted file mode 100644 index 5682c2a0..00000000 --- a/Common/Utils/CloudflareNetworks.g.cs +++ /dev/null @@ -1,85 +0,0 @@ -// -// DO NOT EDIT - manual changes are overwritten on the next regeneration. -// -// Cloudflare public proxy IP ranges (https://www.cloudflare.com/ips), baked in as a startup fallback for TrustedProxiesFetcher when the live fetch at application start fails or times out. -// Regenerated by the FetchCloudflareIPs target in Common.csproj, gated on -p:UpdateCloudflareIPs=true. The Update Cloudflare Proxies GitHub workflow is the only caller that passes that flag. -// -// Generated: 2026-04-24T17:08:34Z -// IPv4 SHA256: f02c6d83bc01ab0ae8577160e036d700c7455359bce054df884e5d7d9e4e9e7b -// IPv6 SHA256: 9e9d39e3e83bad00c4decafd53c63fa62029f3d95db68de937d2be28234ca0a9 -using System.Net; - -namespace OpenShock.Common.Utils; - -public static partial class TrustedProxiesFetcher -{ - private static readonly IPNetwork[] CloudflareNetworks = - [ - // 173.245.48.0/20 - new IPNetwork(new IPAddress(0x0030f5adL), prefixLength: 20), - - // 103.21.244.0/22 - new IPNetwork(new IPAddress(0x00f41567L), prefixLength: 22), - - // 103.22.200.0/22 - new IPNetwork(new IPAddress(0x00c81667L), prefixLength: 22), - - // 103.31.4.0/22 - new IPNetwork(new IPAddress(0x00041f67L), prefixLength: 22), - - // 141.101.64.0/18 - new IPNetwork(new IPAddress(0x0040658dL), prefixLength: 18), - - // 108.162.192.0/18 - new IPNetwork(new IPAddress(0x00c0a26cL), prefixLength: 18), - - // 190.93.240.0/20 - new IPNetwork(new IPAddress(0x00f05dbeL), prefixLength: 20), - - // 188.114.96.0/20 - new IPNetwork(new IPAddress(0x006072bcL), prefixLength: 20), - - // 197.234.240.0/22 - new IPNetwork(new IPAddress(0x00f0eac5L), prefixLength: 22), - - // 198.41.128.0/17 - new IPNetwork(new IPAddress(0x008029c6L), prefixLength: 17), - - // 162.158.0.0/15 - new IPNetwork(new IPAddress(0x00009ea2L), prefixLength: 15), - - // 104.16.0.0/13 - new IPNetwork(new IPAddress(0x00001068L), prefixLength: 13), - - // 104.24.0.0/14 - new IPNetwork(new IPAddress(0x00001868L), prefixLength: 14), - - // 172.64.0.0/13 - new IPNetwork(new IPAddress(0x000040acL), prefixLength: 13), - - // 131.0.72.0/22 - new IPNetwork(new IPAddress(0x00480083L), prefixLength: 22), - - // 2400:cb00::/32 - new IPNetwork(new IPAddress([0x24, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - // 2606:4700::/32 - new IPNetwork(new IPAddress([0x26, 0x06, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - // 2803:f800::/32 - new IPNetwork(new IPAddress([0x28, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - // 2405:b500::/32 - new IPNetwork(new IPAddress([0x24, 0x05, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - // 2405:8100::/32 - new IPNetwork(new IPAddress([0x24, 0x05, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - // 2a06:98c0::/29 - new IPNetwork(new IPAddress([0x2a, 0x06, 0x98, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 29), - - // 2c0f:f248::/32 - new IPNetwork(new IPAddress([0x2c, 0x0f, 0xf2, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32), - - ]; -} diff --git a/Common/Utils/ConnectionDetailsFetcher.cs b/Common/Utils/ConnectionDetailsFetcher.cs index 0896b8b3..d01ff849 100644 --- a/Common/Utils/ConnectionDetailsFetcher.cs +++ b/Common/Utils/ConnectionDetailsFetcher.cs @@ -1,4 +1,4 @@ -using OpenShock.Common.Geo; +using OpenShock.Internal.Common.Geo; using System.Net; // ReSharper disable InconsistentNaming diff --git a/Common/Utils/CryptoUtils.cs b/Common/Utils/CryptoUtils.cs deleted file mode 100644 index 15f5bf98..00000000 --- a/Common/Utils/CryptoUtils.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Security.Cryptography; - -namespace OpenShock.Common.Utils; - -public static class CryptoUtils -{ - public static string RandomAlphaNumericString(int length) => RandomNumberGenerator.GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", length); - public static string RandomNumericString(int length) => RandomNumberGenerator.GetString("0123456789", length); -} \ No newline at end of file diff --git a/Common/Utils/GitHashAttribute.cs b/Common/Utils/GitHashAttribute.cs deleted file mode 100644 index 2e2d24e5..00000000 --- a/Common/Utils/GitHashAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Reflection; - -namespace OpenShock.Common.Utils; - -[AttributeUsage(AttributeTargets.Assembly)] -public sealed class GitHashAttribute(string hash) : Attribute -{ - private string Hash { get; } = hash; - - public static readonly string FullHash = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.Hash ?? "error"; -} diff --git a/Common/Utils/GravatarUtils.cs b/Common/Utils/GravatarUtils.cs index 418b737d..59a7e1d5 100644 --- a/Common/Utils/GravatarUtils.cs +++ b/Common/Utils/GravatarUtils.cs @@ -1,5 +1,7 @@ using System.Web; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Utils; public static class GravatarUtils diff --git a/Common/Utils/HashingUtils.cs b/Common/Utils/HashingUtils.cs deleted file mode 100644 index c9574bcb..00000000 --- a/Common/Utils/HashingUtils.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Buffers; -using System.Diagnostics; -using System.Security.Cryptography; -using System.Text; -using BCrypt.Net; -using OpenShock.Common.Models; - -namespace OpenShock.Common.Utils; - -public static class HashingUtils -{ - private const string BCryptPrefix = "bcrypt"; - private const string Pbkdf2Prefix = "pbkdf2"; - private const HashType BCryptHashType = HashType.SHA512; - - private static readonly LatencyEmulator VerifyTiming = new(200, 100); - - public readonly record struct VerifyHashResult(bool Verified, bool NeedsRehash); - private static readonly VerifyHashResult VerifyHashFailureResult = new(false, false); - - /// - /// Hashes string using SHA-256 and returns the result as a lowercase string - /// - /// - /// - public static string HashSha256(string str) - { - Span hashDigest = stackalloc byte[SHA256.HashSizeInBytes]; - - var nAlloc = str.Length <= 512 - ? Encoding.UTF8.GetMaxByteCount(str.Length) - : Encoding.UTF8.GetByteCount(str); - var buffer = nAlloc <= 256 - ? stackalloc byte[nAlloc] - : new byte[nAlloc]; - - var byteCount = Encoding.UTF8.GetBytes(str, buffer); - - SHA256.HashData(buffer[..byteCount], hashDigest); - - return Convert.ToHexStringLower(hashDigest); - } - - private static PasswordHashingAlgorithm PasswordHashingAlgorithmFromPrefix(ReadOnlySpan prefix) - { - return prefix switch - { - BCryptPrefix => PasswordHashingAlgorithm.BCrypt, - Pbkdf2Prefix => PasswordHashingAlgorithm.PBKDF2, - _ => PasswordHashingAlgorithm.Unknown, - }; - } - public static PasswordHashingAlgorithm GetPasswordHashingAlgorithm(ReadOnlySpan combinedHash) - { - int index = combinedHash.IndexOf(':'); - if (index <= 0) return PasswordHashingAlgorithm.Unknown; - - return PasswordHashingAlgorithmFromPrefix(combinedHash[..index]); - } - - public static string HashPassword(string password) - { - return $"{BCryptPrefix}:{BCrypt.Net.BCrypt.EnhancedHashPassword(password, BCryptHashType)}"; - } - - public static VerifyHashResult VerifyPassword(string password, string combinedHash) - { - int index = combinedHash.IndexOf(':'); - if (index <= 0) return VerifyHashFailureResult; - - var algorithm = PasswordHashingAlgorithmFromPrefix(combinedHash.AsSpan(0, index)); - - if (algorithm == PasswordHashingAlgorithm.BCrypt) - { - var start = Stopwatch.GetTimestamp(); - var verified = BCrypt.Net.BCrypt.EnhancedVerify(password, combinedHash[(index + 1)..], BCryptHashType); - var stop = Stopwatch.GetTimestamp(); - VerifyTiming.Record(stop - start); - - return new VerifyHashResult - { - Verified = verified, - NeedsRehash = false - }; - } - - if (algorithm == PasswordHashingAlgorithm.PBKDF2) - { -#pragma warning disable CS0618 // Type or member is obsolete - return new VerifyHashResult - { - Verified = PBKDF2PasswordHasher.Verify(password, combinedHash, customName: Pbkdf2Prefix + ":"), - NeedsRehash = true - }; -#pragma warning restore CS0618 // Type or member is obsolete - } - - return VerifyHashFailureResult; - } - - public static Task VerifyPasswordFake() - { - return Task.Delay(VerifyTiming.GetFake()); - } - - public static string HashToken(string token) - { - // BE CAREFUL, changing this will break leaked token reporting. - return HashSha256(token); - } - - public static VerifyHashResult VerifyToken(string token, string hashedToken) - { - if (string.IsNullOrEmpty(token)) return VerifyHashFailureResult; - - if (hashedToken.Contains('$')) - { - return VerifyPassword(token, hashedToken) with { NeedsRehash = true }; - } - - return new VerifyHashResult(HashToken(token) == hashedToken, false); - } -} diff --git a/Common/Utils/LatencyEmulator.cs b/Common/Utils/LatencyEmulator.cs deleted file mode 100644 index ace3d52a..00000000 --- a/Common/Utils/LatencyEmulator.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace OpenShock.Common.Utils; - -public sealed class LatencyEmulator -{ - // Use object for broad framework compat; replace with `Lock` if desired. - private readonly Lock _gate = new(); - private readonly long[] _buf; - private int _count; - private int _head; - - // Use double to prevent overflow and improve precision of stats. - private double _sum; - private double _sumSq; - - /// - /// Sliding window of timing samples (stored as ticks). - /// Seeds the window with one sample = max(defaultMs, 0). - /// - public LatencyEmulator(int capacity, long defaultMs) - { - if (capacity <= 1) - throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be > 1."); - - ArgumentOutOfRangeException.ThrowIfNegative(defaultMs); - - _buf = new long[capacity]; - - long ticks = defaultMs * TimeSpan.TicksPerMillisecond; - _buf[0] = ticks; - _count = 1; - _head = 1; - - _sum = ticks; - _sumSq = (double)ticks * ticks; - } - - /// - /// Record a timing sample in TICKS (not milliseconds). - /// - public void Record(long elapsedTicks) - { - ArgumentOutOfRangeException.ThrowIfNegative(elapsedTicks); - - lock (_gate) - { - if (_count < _buf.Length) - { - // growing phase: no evictions - _buf[_head] = elapsedTicks; - _count++; - } - else - { - // steady state: evict oldest at _head, then insert - long old = _buf[_head]; - _sum -= old; - _sumSq -= (double)old * old; - - _buf[_head] = elapsedTicks; - } - - _sum += elapsedTicks; - _sumSq += (double)elapsedTicks * elapsedTicks; - - _head = (_head + 1) % _buf.Length; - } - } - - /// - /// Return a simulated timing using current window mean ± Gaussian noise (as a TimeSpan). - /// Clamped to non-negative ticks. - /// - public TimeSpan GetFake() - { - lock (_gate) - { - var (mean, std) = MeanStdUnsafe(); - double noise = std > 0 ? NextGaussian(0, std) : 0; - double value = Math.Max(mean + noise, 0); // clamp at 0 - return TimeSpan.FromTicks((long)Math.Round(value)); - } - } - - /// - /// Returns (meanMs, stdDevMs) - /// - public (double mean, double std) GetStats() - { - double mean, std; - - lock (_gate) - { - (mean, std) = MeanStdUnsafe(); - } - - return (mean / TimeSpan.TicksPerMillisecond, std / TimeSpan.TicksPerMillisecond); - } - - // --- helpers --- - - // Uses maintained sums for O(1) stats - private (double mean, double std) MeanStdUnsafe() - { - switch (_count) - { - case 0: - return (0, 0); - case 1: - double v = _buf[0]; - return (v, 0); - } - - double n = _count; - double mean = _sum / n; - // Unbiased sample variance - double variance = (_sumSq - n * mean * mean) / (n - 1); - double std = Math.Sqrt(Math.Max(variance, 0)); - return (mean, std); - } - - private static double NextGaussian(double mean, double stdDev) - { - // Box–Muller with Random.Shared - double u1 = 1.0 - Random.Shared.NextDouble(); // (0,1] - double u2 = 1.0 - Random.Shared.NextDouble(); - double mag = Math.Sqrt(-2.0 * Math.Log(u1)); - double z0 = mag * Math.Cos(2.0 * Math.PI * u2); - return mean + z0 * stdDev; - } -} diff --git a/Common/Utils/MathUtils.cs b/Common/Utils/MathUtils.cs deleted file mode 100644 index 16efe783..00000000 --- a/Common/Utils/MathUtils.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace OpenShock.Common.Utils; - -public static class MathUtils -{ - private const float EarthRadius = 6371f; - private const float DegToRad = MathF.PI / 180f; - - /// - /// Calculates the distance between two points on the Earth's surface using the Haversine formula. - /// - /// - /// - /// - /// - /// - public static float CalculateHaversineDistance(float lat1, float lon1, float lat2, float lon2) - { - - float latDist = (lat2 - lat1) * DegToRad; - float lonDist = (lon2 - lon1) * DegToRad; - - float latVal = MathF.Sin(latDist / 2f); - float lonVal = MathF.Sin(lonDist / 2f); - float otherVal = MathF.Cos(lat1 * DegToRad) * MathF.Cos(lat2 * DegToRad); - - float a = latVal * latVal + otherVal * (lonVal * lonVal); - float b = 2f * MathF.Atan2(MathF.Sqrt(a), MathF.Sqrt(1f - a)); - - return EarthRadius * b; - } -} diff --git a/Common/Utils/OsTask.cs b/Common/Utils/OsTask.cs deleted file mode 100644 index aededfa9..00000000 --- a/Common/Utils/OsTask.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Serilog; -using System.Runtime.CompilerServices; - -namespace OpenShock.Common.Utils; - -public static class OsTask -{ - public static Task Run(Func function, [CallerFilePath] string path = "", - [CallerMemberName] string member = "", [CallerLineNumber] int line = -1) - { - var task = Task.Run(function); - task.ContinueWith(t => ErrorHandleTask(path, member, line, t), TaskContinuationOptions.OnlyOnFaulted); - return task; - } - - private static void ErrorHandleTask(string path, string member, int line, Task t) - { - var file = Path.GetFileName(path); - Log.Error(t.Exception, - "Error during task execution. {File}::{Member}:{Line} - Stack: {Stack}", - file, member, line, t.Exception?.StackTrace); - } - - public static Task Run(Task function, [CallerFilePath] string path = "", - [CallerMemberName] string member = "", [CallerLineNumber] int line = -1) - { - var task = Task.Run(() => function); - task.ContinueWith( - t => ErrorHandleTask(path, member, line, t), TaskContinuationOptions.OnlyOnFaulted); - return task; - } -} \ No newline at end of file diff --git a/Common/Utils/PBKDF2PasswordHasher.cs b/Common/Utils/PBKDF2PasswordHasher.cs deleted file mode 100644 index dfe4f581..00000000 --- a/Common/Utils/PBKDF2PasswordHasher.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Security.Cryptography; - -namespace OpenShock.Common.Utils; - -[Obsolete("Use PasswordHashingHelpers instead")] -public static class PBKDF2PasswordHasher -{ - /// - /// Size of salt. - /// - private const int SaltSize = 16; - - /// - /// Size of hash. - /// - private const int HashSize = 32; - - private const string DefaultName = "USER"; - - /// - /// Checks if hash is supported. - /// - /// The hash. - /// Custom hash prefix - /// /// Version of the hash - /// Is supported? - private static bool IsHashSupported(string hashString, uint version = 1, string customName = DefaultName) => - hashString.Contains($"{customName}${version}$"); - - - /// - /// Verifies a password against a hash. - /// - /// The password. - /// The hash. - /// Custom hash prefix - /// Version of the hash - /// Could be verified? - [Obsolete("Use PasswordHashingHelpers.VerifyPassword instead", false)] - public static bool Verify(string password, string hashedPassword, uint version = 1, string customName = DefaultName) - { - // Check hash - if (!IsHashSupported(hashedPassword, version, customName)) - throw new NotSupportedException("The hash type is not supported"); - - // Extract iteration and Base64 string - var splittedHashString = hashedPassword.Replace($"{customName}${version}$", "").Split('$'); - var iterations = int.Parse(splittedHashString[0]); - var base64Hash = splittedHashString[1]; - - // Get hash bytes - var hashBytes = Convert.FromBase64String(base64Hash); - - // Get salt - var salt = new byte[SaltSize]; - Array.Copy(hashBytes, 0, salt, 0, SaltSize); - - // Create hash with given salt - using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA512); - var hash = pbkdf2.GetBytes(HashSize); - - // Get result - for (var i = 0; i < HashSize; i++) - if (hashBytes[i + SaltSize] != hash[i]) - return false; - return true; - } -} \ No newline at end of file diff --git a/Common/Utils/TrustedProxiesFetcher.cs b/Common/Utils/TrustedProxiesFetcher.cs deleted file mode 100644 index bc1bc087..00000000 --- a/Common/Utils/TrustedProxiesFetcher.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Net; - -namespace OpenShock.Common.Utils; - -public static partial class TrustedProxiesFetcher -{ - private static readonly HttpClient Client = new(); - - public static readonly string[] PrivateNetworks = - [ - // Loopback - "127.0.0.0/8", - "::1/128", - "::ffff:127.0.0.0/8", - - // Private IPv4 - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - - // Private IPv6 - "fc00::/7", - "fe80::/10", - ]; - - private static readonly IPNetwork[] PrivateNetworksParsed = [.. PrivateNetworks.Select(IPNetwork.Parse)]; - - private static readonly char[] NewLineSeperators = ['\r', '\n', '\t']; - - private static async Task> FetchCloudflareIPs(Uri uri, CancellationToken ct) - { - using var response = await Client.GetAsync(uri, ct); - var stringResponse = await response.Content.ReadAsStringAsync(ct); - - return ParseNetworks(stringResponse); - } - - private static IPNetwork[] ParseNetworks(string response) - { - var lines = response.Split(NewLineSeperators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - var networks = new IPNetwork[lines.Length]; - - for (int i = 0; i < lines.Length; i++) - { - networks[i] = IPNetwork.Parse(lines[i]); - } - - return networks; - } - - private static async Task FetchCloudflareIPs() - { - try - { - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(5)); // Don't want to make application startup slow - var ct = cts.Token; - - var v4Task = FetchCloudflareIPs(new Uri("https://www.cloudflare.com/ips-v4"), ct); - var v6Task = FetchCloudflareIPs(new Uri("https://www.cloudflare.com/ips-v6"), ct); - - await Task.WhenAll(v4Task, v6Task); - - return [.. v4Task.Result, .. v6Task.Result]; - } - catch (Exception) - { - return null; - } - } - - public static async Task GetTrustedNetworksAsync(bool fetch = true) - { - IPNetwork[]? cfProxies = null; - - if (fetch) - { - cfProxies = await FetchCloudflareIPs(); - } - - cfProxies ??= CloudflareNetworks; - - return [.. PrivateNetworksParsed, .. cfProxies]; - } -} \ No newline at end of file diff --git a/Common/Validation/ChatsetMatchers.cs b/Common/Validation/ChatsetMatchers.cs deleted file mode 100644 index 3c7b844a..00000000 --- a/Common/Validation/ChatsetMatchers.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace OpenShock.Common.Validation; - -/// -/// Specialized character set matchers that match strings by unicode codepoints -/// Codepoints and Codeblocks that listed here can be looked up here: by appending "c/{codepoint}", or "b/{codeblock}" respectively without the "0x" on the start of the value -/// Example for codepoint 0x1F495 (💕): -/// Example for codeblock 0x1F600 (Emoticons): -/// -public static class CharsetMatchers -{ - /// - /// Checks if a unicode value is undesired for displaying in user interfaces - /// Undesired values include among other things Emojis, Zalgo text, and Zero-width space - /// - /// String Rune to check for obnoxious characters - /// True if string is safe - public static bool IsUndesiredUserInterfaceCharacter(uint value) - { - return value - is <= 0x0001F // 00000 (C0 Controls) - or (>= 0x0007F and <= 0x000A0) // 00000, 00080 (C0 Controls, C1 Controls) - or (>= 0x002B0 and <= 0x0036F) // 002B0, 00300 - or (>= 0x01400 and <= 0x017FF) // 01400, 01680, 016A0, 01700, 01720, 01740, 01760, 01780 - or (>= 0x01AB0 and <= 0x01AFF) // 01AB0 - or (>= 0x01DC0 and <= 0x01DFF) // 01DC0 - or (>= 0x02000 and <= 0x0209F) // 02000, 02070 - or (>= 0x020D0 and <= 0x021FF) // 020D0, 02100, 02150, 02190 - or (>= 0x02300 and <= 0x023FF) // 02300 - or (>= 0x02460 and <= 0x024FF) // 02460 - or (>= 0x025A0 and <= 0x027BF) // 025A0, 02600, 02700 - or (>= 0x02900 and <= 0x0297F) // 02900 - or (>= 0x02B00 and <= 0x02BFF) // 02B00 - or (>= 0x0FE00 and <= 0x0FE0F) // 0FE00 - or (>= 0x1F000 and <= 0x1F02F) // 1F000 - or >= 0x1F0A0 // 1F0A0, 1F100, 1F200, 1F300, 1F600, 1F650, 1F680, 1F700, 1F780, 1F800, 1F900, 1FA00, 1FA70, 1FB00, 1FF80, 20000, 2A700, 2B740, 2B820, 2CEB0, 2F800, 2FF80, 30000, 3FF80, 4FF80, 5FF80, 6FF80, 7FF80, 8FF80, 9FF80, AFF80, BFF80, CFF80, DFF80, E0000, E0100, EFF80, FFF80, 10FF80 - - // Other bad symbols (https://en.wikipedia.org/wiki/Whitespace_character#Unicode) - or 0x000AD // Soft hyphen - or (>= 0x0180B and <= 0x0180F) // Mongolian format controls - or 0x03000; // Ideographic space - } - - public static bool ContainsUndesiredUserInterfaceCharacters(ReadOnlySpan span) - { - if (span.IsEmpty) return false; - - int i = span.LastIndexOfAnyExceptInRange(' ', '~'); // Find any non-ascii values (Not between space and tilde) - if (i < 0) return false; - - while (i < span.Length) - { - char c = span[i++]; - - uint value; - if (char.IsSurrogate(c)) // UTF-32 handling - { - if (++i >= span.Length) return true; // High surrogate without following low surrogate - - char c2 = span[i]; - - if (!char.IsSurrogatePair(c, c2)) return true; // Ensure valid pair to prevent ConvertToUtf32 from throwing an exception - - value = (uint)char.ConvertToUtf32(c, c2); - } - else - { - value = (uint)c; - } - - if (IsUndesiredUserInterfaceCharacter(value)) return true; - } - - return false; - } -} \ No newline at end of file diff --git a/Common/Validation/UsernameValidator.cs b/Common/Validation/UsernameValidator.cs index 2faca0a0..39d27e7f 100644 --- a/Common/Validation/UsernameValidator.cs +++ b/Common/Validation/UsernameValidator.cs @@ -3,6 +3,10 @@ using OneOf.Types; using OpenShock.Common.Constants; +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Validation; + namespace OpenShock.Common.Validation; public static class UsernameValidator diff --git a/Common/Websocket/WebsockBaseController.cs b/Common/Websocket/WebsockBaseController.cs index 78b29601..a5609aa1 100644 --- a/Common/Websocket/WebsockBaseController.cs +++ b/Common/Websocket/WebsockBaseController.cs @@ -8,6 +8,12 @@ using OpenShock.Common.Problems; using OpenShock.Common.Utils; +using JsonOptions = OpenShock.Common.JsonSerialization.JsonOptions; + +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.Common.Websocket; /// @@ -115,14 +121,14 @@ public async Task Get([FromServices] IHostApplicationLifetime lifetime, Cancella if (!HttpContext.WebSockets.IsWebSocketRequest) { - await WebsocketError.NonWebsocketRequest.WriteAsJsonAsync(HttpContext, LinkedToken); + await WebsocketError.NonWebsocketRequest.WriteAsJsonAsync(HttpContext, JsonOptions.Default, LinkedToken); return; } var connectionPrecondition = await ConnectionPrecondition(); if (connectionPrecondition.IsT1) { - await connectionPrecondition.AsT1.Value.WriteAsJsonAsync(HttpContext, LinkedToken); + await connectionPrecondition.AsT1.Value.WriteAsJsonAsync(HttpContext, JsonOptions.Default, LinkedToken); return; } diff --git a/Cron/Cron.csproj b/Cron/Cron.csproj index 3334a884..d41308e4 100644 --- a/Cron/Cron.csproj +++ b/Cron/Cron.csproj @@ -19,7 +19,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/Cron/Jobs/ClearOldShockerControlLogs.cs b/Cron/Jobs/ClearOldShockerControlLogs.cs index 27733bde..84a76d64 100644 --- a/Cron/Jobs/ClearOldShockerControlLogs.cs +++ b/Cron/Jobs/ClearOldShockerControlLogs.cs @@ -3,6 +3,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Cron.Attributes; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Cron.Jobs; /// diff --git a/Directory.Packages.props b/Directory.Packages.props index 18d65135..fdfd43aa 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - + @@ -27,6 +27,9 @@ + + + diff --git a/LiveControlGateway/Controllers/HubControllerBase.cs b/LiveControlGateway/Controllers/HubControllerBase.cs index f8acb87d..c7c930e3 100644 --- a/LiveControlGateway/Controllers/HubControllerBase.cs +++ b/LiveControlGateway/Controllers/HubControllerBase.cs @@ -19,6 +19,10 @@ using SemVersion = OpenShock.Common.Models.SemVersion; using Timer = System.Timers.Timer; +using OpenShock.Internal.Common.Problems; + +using OpenShock.Internal.Common.Errors; + namespace OpenShock.LiveControlGateway.Controllers; /// @@ -209,7 +213,7 @@ public async Task DisconnectOld() var uptimeMsFp = (double)uptimeMs; - var maxUptimeMs = HardLimits.FirmwareMaxUptime.TotalMilliseconds; + var maxUptimeMs = ApiHardLimits.FirmwareMaxUptime.TotalMilliseconds; if (uptimeMsFp > maxUptimeMs) return null; // Yeah, ok bro. return DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMilliseconds(uptimeMsFp)); diff --git a/LiveControlGateway/Controllers/InstanceDetailsController.cs b/LiveControlGateway/Controllers/InstanceDetailsController.cs index 7aff9dcb..360d6ac5 100644 --- a/LiveControlGateway/Controllers/InstanceDetailsController.cs +++ b/LiveControlGateway/Controllers/InstanceDetailsController.cs @@ -6,6 +6,8 @@ using System.Net.Mime; using System.Reflection; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.LiveControlGateway.Controllers; /// diff --git a/LiveControlGateway/Controllers/LiveControlController.cs b/LiveControlGateway/Controllers/LiveControlController.cs index 44d9e0b9..9859078d 100644 --- a/LiveControlGateway/Controllers/LiveControlController.cs +++ b/LiveControlGateway/Controllers/LiveControlController.cs @@ -25,6 +25,12 @@ using JsonOptions = OpenShock.Common.JsonSerialization.JsonOptions; using Timer = System.Timers.Timer; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Constants; + +using OpenShock.Internal.Common.Problems; + namespace OpenShock.LiveControlGateway.Controllers; /// diff --git a/LiveControlGateway/LcgKeepAlive.cs b/LiveControlGateway/LcgKeepAlive.cs index d9a3020b..8f6aff64 100644 --- a/LiveControlGateway/LcgKeepAlive.cs +++ b/LiveControlGateway/LcgKeepAlive.cs @@ -3,6 +3,8 @@ using OpenShock.LiveControlGateway.Options; using Redis.OM.Contracts; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.LiveControlGateway; /// diff --git a/LiveControlGateway/LifetimeManager/HubLifetime.cs b/LiveControlGateway/LifetimeManager/HubLifetime.cs index bd912706..1257cf53 100644 --- a/LiveControlGateway/LifetimeManager/HubLifetime.cs +++ b/LiveControlGateway/LifetimeManager/HubLifetime.cs @@ -19,6 +19,10 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Extensions; + namespace OpenShock.LiveControlGateway.LifetimeManager; /// diff --git a/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs b/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs index 8493a18e..ed70649d 100644 --- a/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs +++ b/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs @@ -10,6 +10,8 @@ using Redis.OM.Contracts; using StackExchange.Redis; +using OpenShock.Internal.Common.Extensions; + namespace OpenShock.LiveControlGateway.LifetimeManager; /// diff --git a/LiveControlGateway/LifetimeManager/ShockerState.cs b/LiveControlGateway/LifetimeManager/ShockerState.cs index f8470dfc..8e340e1e 100644 --- a/LiveControlGateway/LifetimeManager/ShockerState.cs +++ b/LiveControlGateway/LifetimeManager/ShockerState.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.LiveControlGateway.LifetimeManager; /// diff --git a/LiveControlGateway/LiveControlGateway.csproj b/LiveControlGateway/LiveControlGateway.csproj index 6706e289..13536150 100644 --- a/LiveControlGateway/LiveControlGateway.csproj +++ b/LiveControlGateway/LiveControlGateway.csproj @@ -18,7 +18,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/LiveControlGateway/Options/LcgOptions.cs b/LiveControlGateway/Options/LcgOptions.cs index bb7bff0a..5a2e82b4 100644 --- a/LiveControlGateway/Options/LcgOptions.cs +++ b/LiveControlGateway/Options/LcgOptions.cs @@ -1,7 +1,7 @@ // ReSharper disable InconsistentNaming using Microsoft.Extensions.Options; -using OpenShock.Common.Geo; +using OpenShock.Internal.Common.Geo; using System.ComponentModel.DataAnnotations; namespace OpenShock.LiveControlGateway.Options; diff --git a/LiveControlGateway/PubSub/ApiTokenUpdateSubscriber.cs b/LiveControlGateway/PubSub/ApiTokenUpdateSubscriber.cs index 3b634fa3..27206fb6 100644 --- a/LiveControlGateway/PubSub/ApiTokenUpdateSubscriber.cs +++ b/LiveControlGateway/PubSub/ApiTokenUpdateSubscriber.cs @@ -7,6 +7,10 @@ using OpenShock.LiveControlGateway.Controllers; using StackExchange.Redis; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Extensions; + namespace OpenShock.LiveControlGateway.PubSub; /// diff --git a/SeedE2E/Seeders/ApiTokenSeeder.cs b/SeedE2E/Seeders/ApiTokenSeeder.cs index 9bb20e52..2ca0654e 100644 --- a/SeedE2E/Seeders/ApiTokenSeeder.cs +++ b/SeedE2E/Seeders/ApiTokenSeeder.cs @@ -7,6 +7,10 @@ using OpenShock.Common.Utils; using OpenShock.SeedE2E.Extensions; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class ApiTokenSeeder @@ -25,7 +29,7 @@ public static async Task SeedAsync(OpenShockContext db, ILogger logger) var apiTokenFaker = new Faker() .RuleFor(t => t.Id, f => f.Random.Guid()) .RuleFor(t => t.UserId, f => f.PickRandom(allUserIds)) - .RuleFor(t => t.Name, f => f.Lorem.Word().Truncate(HardLimits.ApiKeyNameMaxLength)) + .RuleFor(t => t.Name, f => f.Lorem.Word().Truncate(ApiHardLimits.ApiKeyNameMaxLength)) .RuleFor(t => t.TokenHash, f => { // Simulate generating a raw key and hashing it diff --git a/SeedE2E/Seeders/DeviceSeeder.cs b/SeedE2E/Seeders/DeviceSeeder.cs index d4a1d9c4..1d1a74cf 100644 --- a/SeedE2E/Seeders/DeviceSeeder.cs +++ b/SeedE2E/Seeders/DeviceSeeder.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class DeviceSeeder @@ -24,7 +26,7 @@ public static async Task SeedAsync(OpenShockContext db, ILogger logger) .RuleFor(d => d.Id, f => f.Random.Guid()) .RuleFor(d => d.Name, f => f.Commerce.ProductName().Truncate(HardLimits.HubNameMaxLength)) .RuleFor(d => d.OwnerId, f => f.PickRandom(allUserIds)) - .RuleFor(d => d.Token, f => f.Random.AlphaNumeric(HardLimits.HubTokenMaxLength)) + .RuleFor(d => d.Token, f => f.Random.AlphaNumeric(ApiHardLimits.HubTokenMaxLength)) .RuleFor(d => d.CreatedAt, f => f.Date.RecentOffset(60).UtcDateTime); // Generate 3 fake devices per user diff --git a/SeedE2E/Seeders/PublicShareSeeder.cs b/SeedE2E/Seeders/PublicShareSeeder.cs index 77bc6231..30c628fe 100644 --- a/SeedE2E/Seeders/PublicShareSeeder.cs +++ b/SeedE2E/Seeders/PublicShareSeeder.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class PublicShareSeeder diff --git a/SeedE2E/Seeders/ShockerSeeder.cs b/SeedE2E/Seeders/ShockerSeeder.cs index b30c48fb..3ea036da 100644 --- a/SeedE2E/Seeders/ShockerSeeder.cs +++ b/SeedE2E/Seeders/ShockerSeeder.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Utils; using Microsoft.Extensions.Logging; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class ShockerSeeder diff --git a/SeedE2E/Seeders/UserActivationRequestSeeder.cs b/SeedE2E/Seeders/UserActivationRequestSeeder.cs index e6967d9b..e460f901 100644 --- a/SeedE2E/Seeders/UserActivationRequestSeeder.cs +++ b/SeedE2E/Seeders/UserActivationRequestSeeder.cs @@ -4,6 +4,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.SeedE2E.Seeders; public static class UserActivationRequestSeeder diff --git a/SeedE2E/Seeders/UserEmailChangeSeeder.cs b/SeedE2E/Seeders/UserEmailChangeSeeder.cs index db464138..5c6eb636 100644 --- a/SeedE2E/Seeders/UserEmailChangeSeeder.cs +++ b/SeedE2E/Seeders/UserEmailChangeSeeder.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.SeedE2E.Seeders; public static class UserEmailChangeSeeder diff --git a/SeedE2E/Seeders/UserNameChangeSeeder.cs b/SeedE2E/Seeders/UserNameChangeSeeder.cs index 72186ca2..9407bde6 100644 --- a/SeedE2E/Seeders/UserNameChangeSeeder.cs +++ b/SeedE2E/Seeders/UserNameChangeSeeder.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class UserNameChangeSeeder diff --git a/SeedE2E/Seeders/UserPasswordResetSeeder.cs b/SeedE2E/Seeders/UserPasswordResetSeeder.cs index 54d59f3b..c17ca11b 100644 --- a/SeedE2E/Seeders/UserPasswordResetSeeder.cs +++ b/SeedE2E/Seeders/UserPasswordResetSeeder.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.SeedE2E.Seeders; public static class UserPasswordResetSeeder diff --git a/SeedE2E/Seeders/UserSeeder.cs b/SeedE2E/Seeders/UserSeeder.cs index 4b6d1dc1..21670f23 100644 --- a/SeedE2E/Seeders/UserSeeder.cs +++ b/SeedE2E/Seeders/UserSeeder.cs @@ -5,6 +5,10 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + +using OpenShock.Internal.Common.Constants; + namespace OpenShock.SeedE2E.Seeders; public static class UserSeeder