diff --git a/API.IntegrationTests/API.IntegrationTests.csproj b/API.IntegrationTests/API.IntegrationTests.csproj index 383a2120..0d4a377d 100644 --- a/API.IntegrationTests/API.IntegrationTests.csproj +++ b/API.IntegrationTests/API.IntegrationTests.csproj @@ -20,7 +20,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 8017adb2..7f50f440 100644 --- a/API.IntegrationTests/Helpers/TestHelper.cs +++ b/API.IntegrationTests/Helpers/TestHelper.cs @@ -9,6 +9,8 @@ using OpenShock.Common.Services.Session; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.IntegrationTests.Helpers; public static class TestHelper @@ -127,7 +129,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, @@ -152,7 +154,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/AdminUsersViewTests.cs b/API.IntegrationTests/Tests/AdminUsersViewTests.cs new file mode 100644 index 00000000..05d43a13 --- /dev/null +++ b/API.IntegrationTests/Tests/AdminUsersViewTests.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenShock.API.IntegrationTests.Helpers; +using OpenShock.Common.OpenShockDb; + +namespace OpenShock.API.IntegrationTests.Tests; + +/// +/// Covers the admin_users_view.password_hash_type mapping. The column is plain text derived +/// from the bcrypt:/pbkdf2: prefix on users.password_hash (not the legacy +/// password_encryption_type Postgres enum), so EF converts it to +/// by matching enum member names case-insensitively. That +/// coupling between the hash prefix and the enum member name is invisible at the call site, hence +/// these tests. +/// +public sealed class AdminUsersViewTests +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required WebApplicationFactory WebApplicationFactory { get; init; } + + [Test] + public async Task PasswordHashType_ForPasswordUser_MapsToBCrypt() + { + var userId = await TestHelper.CreateUserInDb(WebApplicationFactory, "adminviewbcrypt", "adminviewbcrypt@test.org", "SecurePassword123#"); + + await using var scope = WebApplicationFactory.Services.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var hash = await db.Users.Where(u => u.Id == userId).Select(u => u.PasswordHash).FirstAsync(); + await Assert.That(hash).StartsWith("bcrypt:"); + + var view = await db.AdminUsersViews.AsNoTracking().FirstAsync(v => v.Id == userId); + await Assert.That(view.PasswordHashType).IsEqualTo(PasswordHashingAlgorithm.BCrypt); + } + + [Test] + public async Task PasswordHashType_ForOAuthOnlyUser_IsNull() + { + await using var scope = WebApplicationFactory.Services.CreateAsyncScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var userId = Guid.CreateVersion7(); + db.Users.Add(new User + { + Id = userId, + Name = "adminviewoauth", + Email = "adminviewoauth@test.org", + PasswordHash = null, + ActivatedAt = DateTime.UtcNow + }); + await db.SaveChangesAsync(); + + var view = await db.AdminUsersViews.AsNoTracking().FirstAsync(v => v.Id == userId); + await Assert.That(view.PasswordHashType).IsNull(); + } +} diff --git a/API.IntegrationTests/Tests/LcgAssignmentTests.cs b/API.IntegrationTests/Tests/LcgAssignmentTests.cs index 6c92fef3..6aef20b0 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 6b49e1ac..981809ab 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -25,7 +25,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 4538b769..7ea407f6 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 bdd4ae61..7cb89668 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 da212791..beb5c27a 100644 --- a/API/Controller/Account/Authenticated/ChangeUsername.cs +++ b/API/Controller/Account/Authenticated/ChangeUsername.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; 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/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 d01baaaa..6e8692e4 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 07b43995..ffd07a82 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/DeactivateUser.cs b/API/Controller/Admin/DeactivateUser.cs index 25a5e7ce..a2105527 100644 --- a/API/Controller/Admin/DeactivateUser.cs +++ b/API/Controller/Admin/DeactivateUser.cs @@ -18,7 +18,7 @@ public sealed partial class AdminController public async Task DeactivateUser( [FromRoute] Guid userId, [FromQuery(Name = "deleteLater")] bool deleteLater, - [FromQuery][MaxLength(HardLimits.AuditReasonMaxLength)] string? reason, + [FromQuery][MaxLength(ApiHardLimits.AuditReasonMaxLength)] string? reason, [FromServices] IAccountService accountService) { PedanticallyEnsureAdmin(); diff --git a/API/Controller/Admin/DeleteUser.cs b/API/Controller/Admin/DeleteUser.cs index 45349527..8b3955d6 100644 --- a/API/Controller/Admin/DeleteUser.cs +++ b/API/Controller/Admin/DeleteUser.cs @@ -17,7 +17,7 @@ public sealed partial class AdminController [ProducesResponseType(StatusCodes.Status200OK)] public async Task DeleteUser( [FromRoute] Guid userId, - [FromQuery][MaxLength(HardLimits.AuditReasonMaxLength)] string? reason, + [FromQuery][MaxLength(ApiHardLimits.AuditReasonMaxLength)] string? reason, [FromServices] IAccountService accountService) { PedanticallyEnsureAdmin(); diff --git a/API/Controller/Admin/EmailOutboxList.cs b/API/Controller/Admin/EmailOutboxList.cs index 04dae17f..de9501f5 100644 --- a/API/Controller/Admin/EmailOutboxList.cs +++ b/API/Controller/Admin/EmailOutboxList.cs @@ -5,7 +5,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; -using OpenShock.Common.Query; +using OpenShock.Internal.DynamicLinq.Extensions; +using OpenShock.Internal.DynamicLinq.Query; using System.ComponentModel.DataAnnotations; using System.Net.Mime; diff --git a/API/Controller/Admin/GetUsers.cs b/API/Controller/Admin/GetUsers.cs index 37a02a7b..7611aacd 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 @@ -40,7 +42,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/Admin/ReactivateUser.cs b/API/Controller/Admin/ReactivateUser.cs index 190eacb5..d8e3bb45 100644 --- a/API/Controller/Admin/ReactivateUser.cs +++ b/API/Controller/Admin/ReactivateUser.cs @@ -17,7 +17,7 @@ public sealed partial class AdminController [ProducesResponseType(StatusCodes.Status200OK)] public async Task ReactivateUser( [FromRoute] Guid userId, - [FromQuery][MaxLength(HardLimits.AuditReasonMaxLength)] string? reason, + [FromQuery][MaxLength(ApiHardLimits.AuditReasonMaxLength)] string? reason, [FromServices] IAccountService accountService) { PedanticallyEnsureAdmin(); diff --git a/API/Controller/Device/AssignLCG.cs b/API/Controller/Device/AssignLCG.cs index da9a0ef4..cac43819 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 2afa24c6..5cb863b8 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..061f9ebe 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 @@ -52,9 +54,18 @@ private async Task PairInternal(string pairCode) if (pair is null) return Problem(PairError.PairCodeNotFound); await devicePairs.DeleteAsync(pair); - var deviceToken = await _db.Devices.Where(x => x.Id == pair.Id).Select(x => x.Token).FirstOrDefaultAsync(); - if (deviceToken is null) throw new Exception("Device not found for pair code"); + var device = await _db.Devices.Where(x => x.Id == pair.Id).Select(x => new { x.Token, x.OwnerId }).FirstOrDefaultAsync(); + if (device is null) throw new Exception("Device not found for pair code"); + + try + { + await _deviceUpdateService.UpdateDevice(device.OwnerId, pair.Id, DeviceUpdateType.Paired); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to emit paired update for device {DeviceId}", pair.Id); + } - return LegacyDataOk(deviceToken); + return LegacyDataOk(device.Token); } -} \ No newline at end of file +} diff --git a/API/Controller/Device/_ApiController.cs b/API/Controller/Device/_ApiController.cs index 74c7a249..270b6a40 100644 --- a/API/Controller/Device/_ApiController.cs +++ b/API/Controller/Device/_ApiController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OpenShock.API.Services.DeviceUpdate; using OpenShock.Common; using OpenShock.Common.Authentication; using OpenShock.Common.OpenShockDb; @@ -21,12 +22,14 @@ public sealed partial class DeviceController : OpenShockControllerBase { private readonly OpenShockContext _db; private readonly IRedisConnectionProvider _redis; + private readonly IDeviceUpdateService _deviceUpdateService; private readonly ILogger _logger; - public DeviceController(OpenShockContext db, IRedisConnectionProvider redis, ILogger logger) + public DeviceController(OpenShockContext db, IRedisConnectionProvider redis, IDeviceUpdateService deviceUpdateService, ILogger logger) { _db = db; _redis = redis; + _deviceUpdateService = deviceUpdateService; _logger = logger; } } \ No newline at end of file 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 38cfacb5..07f7cd67 100644 --- a/API/Controller/Devices/DevicesController.cs +++ b/API/Controller/Devices/DevicesController.cs @@ -18,6 +18,12 @@ using OpenShock.Common.Utils; 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 @@ -115,7 +121,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"); @@ -182,7 +188,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 b950141f..31b5c4d2 100644 --- a/API/Controller/Sessions/DeleteSessions.cs +++ b/API/Controller/Sessions/DeleteSessions.cs @@ -5,6 +5,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Problems; +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 000c422c..039b0b20 100644 --- a/API/Controller/Shares/Links/AddShocker.cs +++ b/API/Controller/Shares/Links/AddShocker.cs @@ -7,6 +7,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 b6ffb541..eec3efcd 100644 --- a/API/Controller/Shares/Links/DeletePublicShare.cs +++ b/API/Controller/Shares/Links/DeletePublicShare.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.OpenShockDb; +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 0c67eef5..b30adc8b 100644 --- a/API/Controller/Shares/Links/EditShocker.cs +++ b/API/Controller/Shares/Links/EditShocker.cs @@ -7,6 +7,8 @@ using OpenShock.Common.OpenShockDb; 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 6da1aa4a..a350cd3d 100644 --- a/API/Controller/Shares/Links/PauseShocker.cs +++ b/API/Controller/Shares/Links/PauseShocker.cs @@ -9,6 +9,8 @@ using OpenShock.Common.OpenShockDb; 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 361684be..c452d5d5 100644 --- a/API/Controller/Shares/Links/RemoveShocker.cs +++ b/API/Controller/Shares/Links/RemoveShocker.cs @@ -8,6 +8,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.OpenShockDb; +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 67fec6ef..44fb896d 100644 --- a/API/Controller/Shares/UserShares/CreateShareInvite.cs +++ b/API/Controller/Shares/UserShares/CreateShareInvite.cs @@ -9,6 +9,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 edf65dc9..dc6c8851 100644 --- a/API/Controller/Shares/UserShares/Invites.cs +++ b/API/Controller/Shares/UserShares/Invites.cs @@ -13,6 +13,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 cb6d21ab..d47e7503 100644 --- a/API/Controller/Shares/UserShares/UpdateShockerShares.cs +++ b/API/Controller/Shares/UserShares/UpdateShockerShares.cs @@ -10,6 +10,8 @@ using OpenShock.Common.OpenShockDb; 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 6cafad77..67622006 100644 --- a/API/Controller/Shockers/EditShocker.cs +++ b/API/Controller/Shockers/EditShocker.cs @@ -10,6 +10,8 @@ using OpenShock.Common.OpenShockDb; 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 257c2d5d..321b5978 100644 --- a/API/Controller/Shockers/PauseShocker.cs +++ b/API/Controller/Shockers/PauseShocker.cs @@ -10,6 +10,8 @@ using OpenShock.Common.OpenShockDb; 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 6cf852b9..94c2b311 100644 --- a/API/Controller/Shockers/RemoveShocker.cs +++ b/API/Controller/Shockers/RemoveShocker.cs @@ -10,6 +10,8 @@ using OpenShock.Common.OpenShockDb; 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 04191760..349dfea3 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 2f5b19aa..48352e6e 100644 --- a/API/Controller/Tokens/Tokens.cs +++ b/API/Controller/Tokens/Tokens.cs @@ -9,6 +9,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 1bb89833..da8ec6a4 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 cebfdfb4..8be58a62 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 c9276bb1..02ea0a71 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 01ea346e..d21c61cb 100644 --- a/API/Models/Requests/NewShocker.cs +++ b/API/Models/Requests/NewShocker.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.OpenShockDb; +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 1383acf1..16400755 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; /// @@ -52,7 +54,7 @@ public AccountService(OpenShockContext db, IRedisPubService redisPubService, ISe /// sends, so this value is never the one delivered. It exists only to keep the column populated. /// private static string SeedTokenHash() - => HashingUtils.HashToken(CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength)); + => HashingUtils.HashToken(CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength)); /// /// Best-effort nudge to the email outbox delivery job that a message was enqueued. Deliberately 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 f144b46a..317f55d2 100644 --- a/API/Services/Token/ApiTokenService.cs +++ b/API/Services/Token/ApiTokenService.cs @@ -11,6 +11,8 @@ using OpenShock.Common.Services.RedisPubSub; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.API.Services.Token; /// @@ -112,7 +114,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 { @@ -155,7 +157,7 @@ await _auditService.LogAsync( /// 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 deleted file mode 100644 index f4073f0a..00000000 --- a/Common.Tests/Utils/CryptoUtilsTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using OpenShock.Common.Utils; - -namespace OpenShock.Common.Tests.Utils; - -public class CryptoUtilsTests -{ - [Test] - [Arguments(1)] - [Arguments(10)] - [Arguments(64)] - [Arguments(256)] - public async Task RandomAlphaNumericString_CorrectLength(int length) - { - var result = CryptoUtils.RandomAlphaNumericString(length); - await Assert.That(result.Length).IsEqualTo(length); - } - - [Test] - public async Task RandomAlphaNumericString_OnlyAlphaNumericChars() - { - var result = CryptoUtils.RandomAlphaNumericString(1000); - await Assert.That(result.All(c => char.IsLetterOrDigit(c))).IsTrue(); - } - - [Test] - public async Task RandomAlphaNumericString_ContainsVariety() - { - // With 1000 chars, should have both letters and digits - var result = CryptoUtils.RandomAlphaNumericString(1000); - await Assert.That(result.Any(char.IsLetter)).IsTrue(); - await Assert.That(result.Any(char.IsDigit)).IsTrue(); - } - - [Test] - public async Task RandomAlphaNumericString_TwoCallsProduceDifferentResults() - { - var a = CryptoUtils.RandomAlphaNumericString(32); - var b = CryptoUtils.RandomAlphaNumericString(32); - await Assert.That(a).IsNotEqualTo(b); - } - - [Test] - [Arguments(1)] - [Arguments(6)] - [Arguments(10)] - [Arguments(100)] - public async Task RandomNumericString_CorrectLength(int length) - { - var result = CryptoUtils.RandomNumericString(length); - await Assert.That(result.Length).IsEqualTo(length); - } - - [Test] - public async Task RandomNumericString_OnlyDigits() - { - var result = CryptoUtils.RandomNumericString(1000); - await Assert.That(result.All(char.IsDigit)).IsTrue(); - } - - [Test] - public async Task RandomNumericString_TwoCallsProduceDifferentResults() - { - var a = CryptoUtils.RandomNumericString(32); - var b = CryptoUtils.RandomNumericString(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/LatencyEmulatorTests.cs b/Common.Tests/Utils/LatencyEmulatorTests.cs deleted file mode 100644 index 09381856..00000000 --- a/Common.Tests/Utils/LatencyEmulatorTests.cs +++ /dev/null @@ -1,207 +0,0 @@ -using OpenShock.Common.Utils; -// ReSharper disable ObjectCreationAsStatement - -namespace OpenShock.Common.Tests.Utils; - -public class LatencyEmulatorTests -{ - // --- helpers --- - private static long Ms(double ms) => (long)(ms * TimeSpan.TicksPerMillisecond); - - private static (double mean, double std) MeanStd(IEnumerable samples) - { - var arr = samples.Select(x => (double)x).ToArray(); - double n = arr.Length; - double mean = arr.Average(); - if (n <= 1) return (mean, 0); - - double sumSq = arr.Sum(x => x * x); - double variance = (sumSq - n * mean * mean) / (n - 1); - return (mean, Math.Sqrt(Math.Max(0, variance))); - } - - private const double Eps = 1e-7; - - // --- constructor & basic stats --- - - [Test] - public async Task Ctor_SeedsWithDefaultMs_StatsMatch() - { - var seedMs = 12; - var emu = new LatencyEmulator(capacity: 8, defaultMs: seedMs); - - var (mean, std) = emu.GetStats(); - await Assert.That(mean).IsEqualTo(seedMs).Within(0.5); - await Assert.That(std).IsEqualTo(0); - } - - [Test] - public void Ctor_DefaultMs_Negative_Throws() - { - Assert.Throws(() => new LatencyEmulator(capacity: 8, -1)); - } - - [Test] - public void Ctor_Capacity_OneOrLess_Throws() - { - Assert.Throws(() => new LatencyEmulator(1, 0)); - Assert.Throws(() => new LatencyEmulator(0, 0)); - Assert.Throws(() => new LatencyEmulator(-5, 0)); - } - - // --- Record: input validation --- - - [Test] - public void Record_Negative_Throws() - { - var emu = new LatencyEmulator(capacity: 4, defaultMs: 0); - Assert.Throws(() => emu.Record(-1)); - } - - // --- Record: growth phase (no eviction) --- - - [Test] - public async Task Record_Growing_NoEvictions_StatsMatchAllSamples() - { - var emu = new LatencyEmulator(capacity: 8, defaultMs: 0); - - // Add positive tick samples - long[] add = [ 1, 3, 5 ]; - foreach (var t in add) emu.Record(Ms(t)); - - // window should contain [0, 1 ms, 3 ms, 5 ms] (ticks) - var expected = new List { 0 }; - expected.AddRange(add); - - var (expMean, expStd) = MeanStd(expected); - var (mean, std) = emu.GetStats(); - - await Assert.That(mean).IsEqualTo(expMean).Within(Eps); - await Assert.That(std).IsEqualTo(expStd).Within(Eps); - } - - // --- Record: steady state (with eviction) --- - - [Test] - public async Task Record_EvictsOldest_MaintainsSlidingWindow() - { - // capacity 3, seed 0 ms => window starts [0] - var emu = new LatencyEmulator(capacity: 3, defaultMs: 0); - - // Fill to capacity: [0, 10 ms, 20 ms] - emu.Record(Ms(10)); - emu.Record(Ms(20)); - - var (m1, s1) = emu.GetStats(); - long[] expected1 = [ 0, 10, 20 ]; - var (expM1, expS1) = MeanStd(expected1); - await Assert.That(m1).IsEqualTo(expM1).Within(Eps); - await Assert.That(s1).IsEqualTo(expS1).Within(Eps); - - // Next insert 30 ms => evict the oldest (0), new window [10,20,30] - emu.Record(Ms(30)); - var (m2, s2) = emu.GetStats(); - long[] expected2 = [ 10, 20, 30 ]; - var (expM2, expS2) = MeanStd(expected2); - await Assert.That(m2).IsEqualTo(expM2).Within(Eps); - await Assert.That(s2).IsEqualTo(expS2).Within(Eps); - - // Next insert 40 ms => window [20,30,40] - emu.Record(Ms(40)); - var (m3, s3) = emu.GetStats(); - long[] expected3 = [ 20, 30, 40 ]; - var (expM3, expS3) = MeanStd(expected3); - await Assert.That(m3).IsEqualTo(expM3).Within(Eps); - await Assert.That(s3).IsEqualTo(expS3).Within(Eps); - } - - // --- GetFake() behavior --- - - [Test] - public async Task GetFake_WhenStdZero_ReturnsMeanExactly() - { - // Make all samples identical so std==0 - var emu = new LatencyEmulator(capacity: 5, defaultMs: 7); - var same = Ms(7.0); - emu.Record(same); - emu.Record(same); - emu.Record(same); - emu.Record(same); - - var (mean, std) = emu.GetStats(); - await Assert.That(std).IsEqualTo(0); - - // Without noise, fake should be exactly the mean (with rounding) - var fake = emu.GetFake(); - await Assert.That(fake.TotalMilliseconds).IsEqualTo(mean).Within(Eps); - } - - [Test] - public async Task GetFake_NonZeroStd_NonNegative_AndVaries() - { - var emu = new LatencyEmulator(capacity: 16, defaultMs: 0); - - // Create a spread so std > 0 - foreach (var ms in new[] { 1, 2, 3, 5, 8, 13, 21, 34 }) emu.Record(Ms(ms)); - - var (mean, std) = emu.GetStats(); - await Assert.That(std).IsGreaterThan(0); - - // Gather many samples; all must be non-negative, - // and at least one should differ from a rounded mean. - var fakes = new List(); - for (int i = 0; i < 200; i++) fakes.Add(emu.GetFake().Ticks); - - await Assert.That(fakes).DoesNotContain(x => x < 0); - await Assert.That(fakes).ContainsOnly(x => Math.Abs(x - Math.Round(mean)) > 0); - } - - // --- Numerical stability & precision --- - - [Test] - public async Task Stats_UnbiasedSampleStd_MatchesReference() - { - var emu = new LatencyEmulator(capacity: 8, defaultMs: 0); - long[] vals = [ Ms(10), Ms(20), Ms(30), Ms(40), Ms(50) ]; - foreach (var v in vals) emu.Record(Ms(v)); - - // Window: [0,10,20,30,40,50] (n=6) all in ms converted to ticks - var expected = new long[] { 0 }.Concat(vals).ToArray(); - var (expMean, expStd) = MeanStd(expected); - - var (mean, std) = emu.GetStats(); - await Assert.That(mean).IsEqualTo(expMean).Within(Eps); - await Assert.That(std).IsEqualTo(expStd).Within(Eps); - } - - // --- Concurrency sanity check (no exceptions, stats sane) --- - - [Test] - public async Task Record_IsThreadSafe_Sanity() - { - var emu = new LatencyEmulator(capacity: 128, defaultMs: 1); - - var tasks = Enumerable.Range(0, Environment.ProcessorCount) - .Select(i => Task.Run(() => - { - var rnd = new Random(i * 7919 + 17); - for (int k = 0; k < 5000; k++) - { - // Generate strictly positive tick values (~ up to 10 ms) - // Ensure >= 1 tick to satisfy ThrowIfNegativeOrZero. - long ticks = Math.Max(1, TimeSpan.FromMilliseconds(rnd.NextDouble() * 10).Ticks); - emu.Record(ticks); - } - })) - .ToArray(); - - await Task.WhenAll(tasks); - - var (mean, std) = emu.GetStats(); - // Just make sure we didn’t corrupt numeric state. - await Assert.That(double.IsNaN(mean) || double.IsInfinity(mean)).IsFalse(); - await Assert.That(double.IsNaN(std) || double.IsInfinity(std)).IsFalse(); - await Assert.That(mean).IsGreaterThan(0); - await Assert.That(std).IsGreaterThanOrEqualTo(0); - } -} diff --git a/Common.Tests/Utils/MathUtilsTests.cs b/Common.Tests/Utils/MathUtilsTests.cs deleted file mode 100644 index 73497fc0..00000000 --- a/Common.Tests/Utils/MathUtilsTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using OpenShock.Common.Utils; - -namespace OpenShock.Common.Tests.Utils; - -public class MathUtilsTests -{ - [Test] - public async Task SamePoint_ReturnsZero() - { - var result = MathUtils.CalculateHaversineDistance(0f, 0f, 0f, 0f); - await Assert.That(result).IsEqualTo(0f); - } - - [Test] - public async Task SameCoordinates_ReturnsZero() - { - var result = MathUtils.CalculateHaversineDistance(52.52f, 13.405f, 52.52f, 13.405f); - await Assert.That(result).IsEqualTo(0f); - } - - [Test] - public async Task NewYork_To_London_ApproximatelyCorrect() - { - // NYC: 40.7128, -74.0060 London: 51.5074, -0.1278 - // Expected: ~5570 km - var result = MathUtils.CalculateHaversineDistance(40.7128f, -74.006f, 51.5074f, -0.1278f); - await Assert.That(result).IsGreaterThan(5500f); - await Assert.That(result).IsLessThan(5650f); - } - - [Test] - public async Task NorthPole_To_SouthPole_ApproximatelyHalfCircumference() - { - // ~20015 km - var result = MathUtils.CalculateHaversineDistance(90f, 0f, -90f, 0f); - await Assert.That(result).IsGreaterThan(19900f); - await Assert.That(result).IsLessThan(20100f); - } - - [Test] - public async Task Equator_QuarterWayAround_ApproximatelyCorrect() - { - // 0,0 to 0,90 — quarter circumference at equator ~10008 km - var result = MathUtils.CalculateHaversineDistance(0f, 0f, 0f, 90f); - await Assert.That(result).IsGreaterThan(9900f); - await Assert.That(result).IsLessThan(10100f); - } - - [Test] - public async Task IsSymmetric() - { - var ab = MathUtils.CalculateHaversineDistance(48.8566f, 2.3522f, 35.6762f, 139.6503f); - var ba = MathUtils.CalculateHaversineDistance(35.6762f, 139.6503f, 48.8566f, 2.3522f); - await Assert.That(MathF.Abs(ab - ba)).IsLessThan(0.01f); - } - - [Test] - public async Task AntipodalPoints_ApproximatelyHalfCircumference() - { - // 0,0 to 0,180 — half circumference ~20015 km - var result = MathUtils.CalculateHaversineDistance(0f, 0f, 0f, 180f); - await Assert.That(result).IsGreaterThan(19900f); - await Assert.That(result).IsLessThan(20100f); - } -} 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 16036898..c920230c 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -19,6 +19,9 @@ + + + @@ -38,8 +41,6 @@ - - diff --git a/Common/Constants/ApiHardLimits.cs b/Common/Constants/ApiHardLimits.cs new file mode 100644 index 00000000..23e01a90 --- /dev/null +++ b/Common/Constants/ApiHardLimits.cs @@ -0,0 +1,37 @@ +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 EmailOutboxLastErrorMaxLength = 1024; + public const int EmailOutboxCoalesceKeyMaxLength = 128; + + public const int UserEmailChangeSecretMaxLength = 128; + public const int UserActivationRequestSecretMaxLength = 128; + public const int PasswordResetSecretMaxLength = 100; + public const int AuditReasonMaxLength = 512; + + 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 7ff265bc..00000000 --- a/Common/Constants/HardLimits.cs +++ /dev/null @@ -1,66 +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 EmailOutboxLastErrorMaxLength = 1024; - public const int EmailOutboxCoalesceKeyMaxLength = 128; - - public const int UserEmailChangeSecretMaxLength = 128; - public const int UserActivationRequestSecretMaxLength = 128; - public const int PasswordResetSecretMaxLength = 100; - public const int ShockerControlLogCustomNameMaxLength = 64; - public const int AuditReasonMaxLength = 512; - - 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 7753103b..9e487d36 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 159533a8..3522baf8 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/DeviceUpdateType.cs b/Common/Models/DeviceUpdateType.cs index 9b5f337f..f2e77ddf 100644 --- a/Common/Models/DeviceUpdateType.cs +++ b/Common/Models/DeviceUpdateType.cs @@ -5,6 +5,6 @@ public enum DeviceUpdateType Created, // Whenever a new device is created Updated, // Whenever name or something else directly related to the device is updated ShockerUpdated, // Whenever a shocker is updated, name or limits for a person - Deleted // Whenever a device is deleted - + Deleted, // Whenever a device is deleted + Paired // Whenever a device consumes a pair code } \ No newline at end of file diff --git a/Common/Models/WebSocket/User/Control.cs b/Common/Models/WebSocket/User/Control.cs index 21e3b32f..4aad3a7b 100644 --- a/Common/Models/WebSocket/User/Control.cs +++ b/Common/Models/WebSocket/User/Control.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.OpenShockDb; +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 3e06b99a..8194fdda 100644 --- a/Common/Models/WebSocket/User/ControlLog.cs +++ b/Common/Models/WebSocket/User/ControlLog.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.OpenShockDb; +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 36896c69..166f46d0 100644 --- a/Common/OpenShockControllerBase.cs +++ b/Common/OpenShockControllerBase.cs @@ -3,17 +3,17 @@ using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; 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/Models/ApiToken.cs b/Common/OpenShockDb/Models/ApiToken.cs index 343e957b..1d2270d6 100644 --- a/Common/OpenShockDb/Models/ApiToken.cs +++ b/Common/OpenShockDb/Models/ApiToken.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.Constants; +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 21bfe155..4a03292f 100644 --- a/Common/OpenShockDb/OpenShockContext.cs +++ b/Common/OpenShockDb/OpenShockContext.cs @@ -5,6 +5,8 @@ using OpenShock.Common.Extensions; using OpenShock.Common.Models; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Common.OpenShockDb; /// @@ -154,7 +156,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") @@ -254,7 +256,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) @@ -276,7 +278,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) @@ -306,7 +308,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"); @@ -611,7 +613,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()") @@ -664,7 +666,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"); @@ -727,7 +729,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"); @@ -845,7 +847,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") @@ -881,7 +883,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnType("jsonb") .HasColumnName("payload"); entity.Property(e => e.CoalesceKey) - .VarCharWithLength(HardLimits.EmailOutboxCoalesceKeyMaxLength) + .VarCharWithLength(ApiHardLimits.EmailOutboxCoalesceKeyMaxLength) .HasColumnName("coalesce_key"); entity.Property(e => e.Status) .HasColumnName("status"); @@ -898,7 +900,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasDefaultValueSql("CURRENT_TIMESTAMP") .HasColumnName("next_attempt_at"); entity.Property(e => e.LastError) - .VarCharWithLength(HardLimits.EmailOutboxLastErrorMaxLength) + .VarCharWithLength(ApiHardLimits.EmailOutboxLastErrorMaxLength) .HasColumnName("last_error"); entity.Property(e => e.CreatedAt) .HasDefaultValueSql("CURRENT_TIMESTAMP") @@ -977,7 +979,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasColumnType("audit_action") .HasColumnName("action"); entity.Property(e => e.Reason) - .VarCharWithLength(HardLimits.AuditReasonMaxLength) + .VarCharWithLength(ApiHardLimits.AuditReasonMaxLength) .HasColumnName("reason"); entity.Property(e => e.IpAddress) .VarCharWithLength(HardLimits.IpAddressMaxLength) diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 45f5dabd..fef56cbc 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -14,6 +14,8 @@ using Serilog.Events; 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 46b8c5cd..43ad1dcd 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -11,7 +11,6 @@ using OpenShock.Common.Authentication; using OpenShock.Common.Authentication.AuthenticationHandlers; using OpenShock.Common.Authentication.Services; -using OpenShock.Common.ExceptionHandle; using OpenShock.Common.HealthChecks; using OpenShock.Common.JsonSerialization; using OpenShock.Common.OpenShockDb; @@ -30,6 +29,7 @@ using System.Threading.RateLimiting; using OpenShock.Common.Extensions; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.ExceptionHandling; using JsonOptions = OpenShock.Common.JsonSerialization.JsonOptions; namespace OpenShock.Common; @@ -133,6 +133,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.AddCachedHealthChecks(HealthCheckCacheTtl); 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 334179bb..d634124b 100644 --- a/Common/Problems/CustomProblems/TokenPermissionProblem.cs +++ b/Common/Problems/CustomProblems/TokenPermissionProblem.cs @@ -1,6 +1,8 @@ using System.Net; using OpenShock.Common.OpenShockDb; +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 18d82f27..04bc83f1 100644 --- a/Common/Services/BatchUpdate/BatchUpdateService.cs +++ b/Common/Services/BatchUpdate/BatchUpdateService.cs @@ -6,6 +6,8 @@ using OpenShock.Common.Utils; using StackExchange.Redis; +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 c3e1723c..f5d7d3fb 100644 --- a/Common/Services/Session/SessionService.cs +++ b/Common/Services/Session/SessionService.cs @@ -9,6 +9,8 @@ using Redis.OM.Contracts; using Redis.OM.Searching; +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Services.Session; /// @@ -33,7 +35,7 @@ public SessionService(IRedisConnectionProvider redisConnectionProvider, IAuditSe public async Task CreateSessionAsync(Guid userId, string userAgent, string ipAddress, Guid? actorId) { 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 index b375b538..57dbc2d2 100644 --- a/Common/Utils/HashingUtils.cs +++ b/Common/Utils/HashingUtils.cs @@ -4,6 +4,10 @@ using BCrypt.Net; using OpenShock.Common.OpenShockDb; +// LatencyEmulator and PBKDF2PasswordHasher stay in OpenShock.Internal.Common; only the password +// hashing policy itself is kept local. +using OpenShock.Internal.Common.Utils; + namespace OpenShock.Common.Utils; public static class HashingUtils 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.IntegrationTests/Cron.IntegrationTests.csproj b/Cron.IntegrationTests/Cron.IntegrationTests.csproj index a3886b42..d3af8f27 100644 --- a/Cron.IntegrationTests/Cron.IntegrationTests.csproj +++ b/Cron.IntegrationTests/Cron.IntegrationTests.csproj @@ -28,7 +28,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/Cron.IntegrationTests/Docker/InMemoryDatabase.cs b/Cron.IntegrationTests/Docker/InMemoryDatabase.cs index 9ce428e4..49c03810 100644 --- a/Cron.IntegrationTests/Docker/InMemoryDatabase.cs +++ b/Cron.IntegrationTests/Docker/InMemoryDatabase.cs @@ -1,4 +1,5 @@ using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; using Testcontainers.PostgreSql; using TUnit.Core.Interfaces; @@ -19,7 +20,7 @@ public PostgreSqlContainer Container .WithName($"tunit-cron-postgresql-{Guid.CreateVersion7()}") .WithDatabase("openshock") .WithUsername("openshock") - .WithPassword(CryptoUtils.RandomAlphaNumericString(32)) + .WithPassword(CryptoUtils.RandomString(32)) .Build(); return _container; diff --git a/Cron.IntegrationTests/Tests/ControlLogRetentionTests.cs b/Cron.IntegrationTests/Tests/ControlLogRetentionTests.cs index e4f436d9..e18531ab 100644 --- a/Cron.IntegrationTests/Tests/ControlLogRetentionTests.cs +++ b/Cron.IntegrationTests/Tests/ControlLogRetentionTests.cs @@ -3,6 +3,7 @@ using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; namespace OpenShock.Cron.IntegrationTests.Tests; @@ -114,7 +115,7 @@ private static void AddShocker(OpenShockContext db, Guid shockerId, Guid ownerId db.Devices.Add(new Device { Id = deviceId, OwnerId = ownerId, Name = "LogTrimHub", - Token = CryptoUtils.RandomAlphaNumericString(256), CreatedAt = DateTime.UtcNow + Token = CryptoUtils.RandomString(256), CreatedAt = DateTime.UtcNow }); db.Shockers.Add(new Shocker { diff --git a/Cron.IntegrationTests/Tests/EmailOutboxDeliveryTests.cs b/Cron.IntegrationTests/Tests/EmailOutboxDeliveryTests.cs index 32ecf588..56cef816 100644 --- a/Cron.IntegrationTests/Tests/EmailOutboxDeliveryTests.cs +++ b/Cron.IntegrationTests/Tests/EmailOutboxDeliveryTests.cs @@ -4,6 +4,7 @@ using OpenShock.Common.Models; using OpenShock.Common.OpenShockDb; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; namespace OpenShock.Cron.IntegrationTests.Tests; @@ -132,7 +133,7 @@ public async Task PasswordReset_NewerRequest_SupersedesOlder_OnlyNewestDelivered { Id = Guid.CreateVersion7(), UserId = userId, - TokenHash = HashingUtils.HashToken(CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength)), + TokenHash = HashingUtils.HashToken(CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength)), SecurityStampAtCreate = stamp, CreatedAt = DateTime.UtcNow }; diff --git a/Cron/Cron.csproj b/Cron/Cron.csproj index 054f49c6..04c33294 100644 --- a/Cron/Cron.csproj +++ b/Cron/Cron.csproj @@ -30,7 +30,7 @@ - + <_Parameter1>$(SourceRevisionId) diff --git a/Cron/Jobs/ClearOldShockerControlLogs.cs b/Cron/Jobs/ClearOldShockerControlLogs.cs index 5f2cf198..47424e67 100644 --- a/Cron/Jobs/ClearOldShockerControlLogs.cs +++ b/Cron/Jobs/ClearOldShockerControlLogs.cs @@ -2,6 +2,8 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Cron.Attributes; +using OpenShock.Internal.Common.Constants; + namespace OpenShock.Cron.Jobs; /// diff --git a/Cron/Services/Email/Outbox/EmailOutboxDispatcher.cs b/Cron/Services/Email/Outbox/EmailOutboxDispatcher.cs index 88046ceb..42e453c3 100644 --- a/Cron/Services/Email/Outbox/EmailOutboxDispatcher.cs +++ b/Cron/Services/Email/Outbox/EmailOutboxDispatcher.cs @@ -5,6 +5,7 @@ using OpenShock.Common.OpenShockDb; using OpenShock.Common.Options; using OpenShock.Common.Utils; +using OpenShock.Internal.Common.Utils; namespace OpenShock.Cron.Services.Email.Outbox; @@ -146,7 +147,7 @@ private async Task SendPreview(EmailOutboxMessage message, /// private static async Task MintTokenAsync(OpenShockContext db, Action applyHash, CancellationToken ct) { - var token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength); + var token = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength); applyHash(HashingUtils.HashToken(token)); await db.SaveChangesAsync(ct); return token; diff --git a/Cron/Services/Email/Outbox/EmailOutboxStateMachine.cs b/Cron/Services/Email/Outbox/EmailOutboxStateMachine.cs index f90ca38a..b0293a63 100644 --- a/Cron/Services/Email/Outbox/EmailOutboxStateMachine.cs +++ b/Cron/Services/Email/Outbox/EmailOutboxStateMachine.cs @@ -28,7 +28,7 @@ public static bool TryClaim(EmailOutboxMessage message, DateTime nowUtc) { message.Status = EmailStatus.Failed; message.FailedAt = nowUtc; - message.LastError = $"Exhausted after {message.AttemptCount - 1} attempt(s) (delivery did not complete)".Truncate(HardLimits.EmailOutboxLastErrorMaxLength); + message.LastError = $"Exhausted after {message.AttemptCount - 1} attempt(s) (delivery did not complete)".Truncate(ApiHardLimits.EmailOutboxLastErrorMaxLength); return false; } @@ -58,17 +58,17 @@ public static void ApplyResult(EmailOutboxMessage message, EmailDispatchResult r // A successful no-op (request used/expired/superseded/gone). Distinct from Failed so it // is never mistaken for a delivery problem or requeued. message.Status = EmailStatus.Skipped; - message.LastError = result.Detail?.Truncate(HardLimits.EmailOutboxLastErrorMaxLength); + message.LastError = result.Detail?.Truncate(ApiHardLimits.EmailOutboxLastErrorMaxLength); break; case EmailDispatchOutcome.PermanentFailure: message.Status = EmailStatus.Failed; message.FailedAt = nowUtc; - message.LastError = result.Detail?.Truncate(HardLimits.EmailOutboxLastErrorMaxLength); + message.LastError = result.Detail?.Truncate(ApiHardLimits.EmailOutboxLastErrorMaxLength); break; case EmailDispatchOutcome.TransientFailure: - message.LastError = result.Detail?.Truncate(HardLimits.EmailOutboxLastErrorMaxLength); + message.LastError = result.Detail?.Truncate(ApiHardLimits.EmailOutboxLastErrorMaxLength); if (message.AttemptCount >= EmailOutboxRetryPolicy.MaxAttempts) { message.Status = EmailStatus.Failed; diff --git a/Directory.Packages.props b/Directory.Packages.props index ea74eb3f..161abf87 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,13 +2,15 @@ true + + true - + @@ -29,6 +31,9 @@ + + + @@ -41,6 +46,8 @@ + + diff --git a/LiveControlGateway/Controllers/HubControllerBase.cs b/LiveControlGateway/Controllers/HubControllerBase.cs index aab9f028..80dc7323 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 a10f15c1..aa508c36 100644 --- a/LiveControlGateway/LcgKeepAlive.cs +++ b/LiveControlGateway/LcgKeepAlive.cs @@ -4,6 +4,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 db5c1425..73d505ac 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 fa174de5..7d8e0bd7 100644 --- a/LiveControlGateway/LifetimeManager/ShockerState.cs +++ b/LiveControlGateway/LifetimeManager/ShockerState.cs @@ -2,6 +2,8 @@ using OpenShock.Common.Constants; using OpenShock.Common.OpenShockDb; +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 3ca74612..f998e7a3 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