From 6e35edb71543fd864e84ed13d47b1a0f00b42fd8 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Tue, 14 Oct 2025 21:15:51 +0500 Subject: [PATCH] global: add null safety checks --- .../Authorization/SyncRequirement.cs | 10 +-- .../Controllers/AnnouncementController.cs | 2 +- Notesnook.API/Controllers/InboxController.cs | 10 +-- .../Controllers/MonographsController.cs | 17 ++--- .../Controllers/SyncDeviceController.cs | 4 +- Notesnook.API/Controllers/UsersController.cs | 8 +-- .../Extensions/ClaimsPrincipalExtensions.cs | 3 + Notesnook.API/Jobs/DeviceCleanupJob.cs | 3 +- Notesnook.API/Models/Announcement.cs | 42 ++++++------ .../CompleteMultipartUploadRequestWrapper.cs | 6 +- Notesnook.API/Models/DeleteAccountForm.cs | 2 +- Notesnook.API/Models/EncryptedData.cs | 16 ++--- Notesnook.API/Models/InboxApiKey.cs | 8 +-- Notesnook.API/Models/InboxSyncItem.cs | 20 ++---- Notesnook.API/Models/Monograph.cs | 22 ++---- Notesnook.API/Models/MonographContent.cs | 4 +- Notesnook.API/Models/MonographMetadata.cs | 2 +- Notesnook.API/Models/MultipartUploadMeta.cs | 6 +- Notesnook.API/Models/PartETagWrapper.cs | 2 +- .../Models/Responses/SignupResponse.cs | 4 +- Notesnook.API/Models/S3Options.cs | 8 +-- Notesnook.API/Models/SyncItem.cs | 15 +--- Notesnook.API/Models/UserSettings.cs | 12 ++-- Notesnook.API/Program.cs | 2 +- Notesnook.API/Services/UserService.cs | 6 +- Notesnook.API/Startup.cs | 15 ++-- .../JsonInterfaceConverterAttribute.cs | 33 --------- Streetwriters.Common/Clients.cs | 9 +-- Streetwriters.Common/Constants.cs | 68 +++++++++---------- .../Converters/InterfaceConverter.cs | 54 --------------- .../Extensions/AppBuilderExtensions.cs | 6 +- .../Extensions/HttpClientExtensions.cs | 22 +++--- .../ServiceCollectionServiceExtensions.cs | 2 +- Streetwriters.Common/Interfaces/IClient.cs | 2 +- Streetwriters.Common/Interfaces/IDocument.cs | 4 +- .../Interfaces/IEmailSender.cs | 4 +- Streetwriters.Common/Interfaces/IOffer.cs | 32 --------- Streetwriters.Common/Interfaces/IResponse.cs | 2 +- .../Interfaces/IUserSubscriptionService.cs | 2 +- .../Messages/CreateSubscriptionMessage.cs | 12 ++-- .../Messages/CreateSubscriptionMessageV2.cs | 8 +-- .../Messages/DeleteSubscriptionMessage.cs | 2 +- .../Messages/DeleteUserMessage.cs | 2 +- .../Messages/SendSSEMessage.cs | 8 +-- Streetwriters.Common/Models/Client.cs | 14 ++-- Streetwriters.Common/Models/EmailTemplate.cs | 8 +-- .../Models/GetCustomerResponse.cs | 4 +- .../Models/GetSubscriptionResponse.cs | 14 ++-- .../Models/GetTransactionInvoiceResponse.cs | 4 +- .../Models/GetTransactionResponse.cs | 2 +- Streetwriters.Common/Models/GiftCard.cs | 14 ++-- .../Models/ListPaymentsResponse.cs | 8 +-- .../Models/ListTransactionsResponse.cs | 26 +++---- .../Models/ListTransactionsResponseV2.cs | 48 ++++++------- .../Models/ListUsersResponse.cs | 12 ++-- Streetwriters.Common/Models/MFAConfig.cs | 4 +- Streetwriters.Common/Models/Offer.cs | 10 +-- Streetwriters.Common/Models/PaddleResponse.cs | 2 +- Streetwriters.Common/Models/PromoCode.cs | 2 +- .../Models/RefundPaymentResponse.cs | 2 +- Streetwriters.Common/Models/Response.cs | 2 +- Streetwriters.Common/Models/Subscription.cs | 8 ++- .../Models/SubscriptionPreviewResponse.cs | 20 +++--- Streetwriters.Common/Models/UserModel.cs | 8 +-- Streetwriters.Common/Servers.cs | 18 ++--- Streetwriters.Common/Services/EmailSender.cs | 8 +-- .../Services/PaddleBillingService.cs | 2 +- .../Services/PaddleService.cs | 12 ++-- Streetwriters.Common/WampServers.cs | 8 +-- Streetwriters.Data/DbSettings.cs | 4 +- Streetwriters.Data/Streetwriters.Data.csproj | 1 + .../Controllers/AccountController.cs | 34 +++++----- .../Controllers/MFAController.cs | 8 +-- .../Controllers/SignupController.cs | 11 ++- .../Extensions/HttpContextExtensions.cs | 12 ++-- .../Extensions/MongoDBTicketStore.cs | 5 +- .../Extensions/UrlExtensions.cs | 2 +- .../Extensions/UserManagerExtensions.cs | 4 +- .../Interfaces/IMFAService.cs | 2 +- .../MessageHandlers/CreateSubscription.cs | 2 +- .../MessageHandlers/DeleteSubscription.cs | 2 +- .../Models/AuthenticatorDetails.cs | 4 +- .../Models/ChangeEmailForm.cs | 2 +- .../Models/GetAccessTokenForm.cs | 6 +- .../Models/MFAPasswordRequiredResponse.cs | 2 +- .../Models/MFARequiredResponse.cs | 8 +-- .../Models/MultiFactorEnableForm.cs | 4 +- .../Models/MultiFactorSetupForm.cs | 4 +- .../Models/ResetPasswordForm.cs | 4 +- Streetwriters.Identity/Models/SignupForm.cs | 8 +-- .../Models/TwoFactorLoginForm.cs | 2 +- .../Models/UpdateUserForm.cs | 12 ++-- Streetwriters.Identity/Program.cs | 2 +- .../CustomIntrospectionResponseGenerator.cs | 6 +- Streetwriters.Identity/Services/MFAService.cs | 21 +++--- .../Services/ProfileService.cs | 2 +- .../Services/TemplatedEmailSender.cs | 26 +++---- .../Services/UserAccountService.cs | 6 +- .../Services/UserService.cs | 2 +- Streetwriters.Identity/Startup.cs | 4 +- .../Streetwriters.Identity.csproj | 1 + .../Validation/BearerTokenValidator.cs | 2 +- .../CustomResourceOwnerValidator.cs | 5 +- .../Validation/EmailGrantValidator.cs | 32 +++------ .../Validation/MFAGrantValidator.cs | 2 + .../Validation/MFAPasswordGrantValidator.cs | 2 + Streetwriters.Messenger/Helpers/SSEHelper.cs | 2 +- Streetwriters.Messenger/Program.cs | 2 +- .../Streetwriters.Messenger.csproj | 1 + 109 files changed, 452 insertions(+), 590 deletions(-) delete mode 100644 Streetwriters.Common/Attributes/JsonInterfaceConverterAttribute.cs delete mode 100644 Streetwriters.Common/Converters/InterfaceConverter.cs delete mode 100644 Streetwriters.Common/Interfaces/IOffer.cs diff --git a/Notesnook.API/Authorization/SyncRequirement.cs b/Notesnook.API/Authorization/SyncRequirement.cs index eb249f2..f0a6e75 100644 --- a/Notesnook.API/Authorization/SyncRequirement.cs +++ b/Notesnook.API/Authorization/SyncRequirement.cs @@ -43,7 +43,7 @@ namespace Notesnook.API.Authorization PathString path = context.Resource is DefaultHttpContext httpContext ? httpContext.Request.Path : null; var result = this.IsAuthorized(context.User, path); if (result.Succeeded) context.Succeed(requirement); - else if (result.AuthorizationFailure.FailureReasons.Any()) + else if (result.AuthorizationFailure?.FailureReasons.Any() == true) context.Fail(result.AuthorizationFailure.FailureReasons.First()); else context.Fail(); @@ -63,11 +63,11 @@ namespace Notesnook.API.Authorization return PolicyAuthorizationResult.Forbid(AuthorizationFailure.Failed(reason)); } - var hasSyncScope = User.HasClaim("scope", "notesnook.sync"); - var isInAudience = User.HasClaim("aud", "notesnook"); - var hasRole = User.HasClaim("role", "notesnook"); + var hasSyncScope = User?.HasClaim("scope", "notesnook.sync") ?? false; + var isInAudience = User?.HasClaim("aud", "notesnook") ?? false; + var hasRole = User?.HasClaim("role", "notesnook") ?? false; - var isEmailVerified = User.HasClaim("verified", "true"); + var isEmailVerified = User?.HasClaim("verified", "true") ?? false; if (!isEmailVerified) { diff --git a/Notesnook.API/Controllers/AnnouncementController.cs b/Notesnook.API/Controllers/AnnouncementController.cs index 9b138ed..1a980a2 100644 --- a/Notesnook.API/Controllers/AnnouncementController.cs +++ b/Notesnook.API/Controllers/AnnouncementController.cs @@ -57,7 +57,7 @@ namespace Notesnook.API.Controllers if (item.Type != "callToActions") continue; foreach (var action in item.Actions) { - if (action.Type != "link") continue; + if (action.Type != "link" || action.Data == null) continue; action.Data = action.Data.Replace("{{UserId}}", userId ?? "0"); } diff --git a/Notesnook.API/Controllers/InboxController.cs b/Notesnook.API/Controllers/InboxController.cs index d57b003..888cca3 100644 --- a/Notesnook.API/Controllers/InboxController.cs +++ b/Notesnook.API/Controllers/InboxController.cs @@ -47,7 +47,7 @@ namespace Notesnook.API.Controllers [Authorize(Policy = "Notesnook")] public async Task GetApiKeysAsync() { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { var apiKeys = await inboxApiKeysRepository.FindAsync(t => t.UserId == userId); @@ -64,7 +64,7 @@ namespace Notesnook.API.Controllers [Authorize(Policy = "Notesnook")] public async Task CreateApiKeyAsync([FromBody] InboxApiKey request) { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { if (string.IsNullOrWhiteSpace(request.Name)) @@ -104,7 +104,7 @@ namespace Notesnook.API.Controllers [Authorize(Policy = "Notesnook")] public async Task DeleteApiKeyAsync(string apiKey) { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { if (string.IsNullOrWhiteSpace(apiKey)) @@ -126,7 +126,7 @@ namespace Notesnook.API.Controllers [Authorize(Policy = InboxApiKeyAuthenticationDefaults.AuthenticationScheme)] public async Task GetPublicKeyAsync() { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { var userSetting = await userSettingsRepository.FindOneAsync(u => u.UserId == userId); @@ -147,7 +147,7 @@ namespace Notesnook.API.Controllers [Authorize(Policy = InboxApiKeyAuthenticationDefaults.AuthenticationScheme)] public async Task CreateInboxItemAsync([FromBody] InboxSyncItem request) { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { if (request.Key.Algorithm != Algorithms.XSAL_X25519_7) diff --git a/Notesnook.API/Controllers/MonographsController.cs b/Notesnook.API/Controllers/MonographsController.cs index c0f2d85..52036f4 100644 --- a/Notesnook.API/Controllers/MonographsController.cs +++ b/Notesnook.API/Controllers/MonographsController.cs @@ -98,9 +98,8 @@ namespace Notesnook.API.Controllers { try { - var userId = this.User.FindFirstValue("sub"); + var userId = this.User.GetUserId(); var jti = this.User.FindFirstValue("jti"); - if (userId == null) return Unauthorized(); var existingMonograph = await FindMonographAsync(userId, monograph); if (existingMonograph != null && !existingMonograph.Deleted) return await UpdateAsync(deviceId, monograph); @@ -144,9 +143,8 @@ namespace Notesnook.API.Controllers { try { - var userId = this.User.FindFirstValue("sub"); + var userId = this.User.GetUserId(); var jti = this.User.FindFirstValue("jti"); - if (userId == null) return Unauthorized(); var existingMonograph = await FindMonographAsync(userId, monograph); if (existingMonograph == null || existingMonograph.Deleted) @@ -193,8 +191,7 @@ namespace Notesnook.API.Controllers [HttpGet] public async Task GetUserMonographsAsync() { - var userId = this.User.FindFirstValue("sub"); - if (userId == null) return Unauthorized(); + var userId = this.User.GetUserId(); var userMonographs = (await monographs.Collection.FindAsync( Builders.Filter.And( @@ -257,8 +254,7 @@ namespace Notesnook.API.Controllers [HttpDelete("{id}")] public async Task DeleteAsync([FromQuery] string? deviceId, [FromRoute] string id) { - var userId = this.User.FindFirstValue("sub"); - if (userId is null) return Unauthorized(); + var userId = this.User.GetUserId(); var monograph = await FindMonographAsync(id); if (monograph == null || monograph.Deleted) @@ -310,12 +306,13 @@ namespace Notesnook.API.Controllers }); } - private async Task CleanupContentAsync(ClaimsPrincipal user, string content) + private async Task CleanupContentAsync(ClaimsPrincipal user, string? content) { + if (string.IsNullOrEmpty(content)) return string.Empty; if (Constants.IS_SELF_HOSTED) return content; try { - var json = JsonSerializer.Deserialize(content); + var json = JsonSerializer.Deserialize(content) ?? throw new Exception("Invalid monograph content."); var html = json.Data; if (user.IsUserSubscribed()) diff --git a/Notesnook.API/Controllers/SyncDeviceController.cs b/Notesnook.API/Controllers/SyncDeviceController.cs index 8cf964c..5c8fe98 100644 --- a/Notesnook.API/Controllers/SyncDeviceController.cs +++ b/Notesnook.API/Controllers/SyncDeviceController.cs @@ -44,7 +44,7 @@ namespace Notesnook.API.Controllers { try { - var userId = this.User.FindFirstValue("sub") ?? throw new Exception("User not found."); + var userId = this.User.GetUserId(); new SyncDeviceService(new SyncDevice(userId, deviceId)).RegisterDevice(); return Ok(); } @@ -61,7 +61,7 @@ namespace Notesnook.API.Controllers { try { - var userId = this.User.FindFirstValue("sub") ?? throw new Exception("User not found."); + var userId = this.User.GetUserId(); new SyncDeviceService(new SyncDevice(userId, deviceId)).UnregisterDevice(); return Ok(); } diff --git a/Notesnook.API/Controllers/UsersController.cs b/Notesnook.API/Controllers/UsersController.cs index ede4fa4..183ddb9 100644 --- a/Notesnook.API/Controllers/UsersController.cs +++ b/Notesnook.API/Controllers/UsersController.cs @@ -55,7 +55,7 @@ namespace Notesnook.API.Controllers [HttpGet] public async Task GetUser() { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { UserResponse response = await UserService.GetUserAsync(userId); @@ -72,7 +72,7 @@ namespace Notesnook.API.Controllers [HttpPatch] public async Task UpdateUser([FromBody] UserKeys keys) { - var userId = User.FindFirstValue("sub"); + var userId = User.GetUserId(); try { await UserService.SetUserKeysAsync(userId, keys); @@ -88,7 +88,7 @@ namespace Notesnook.API.Controllers [HttpPost("reset")] public async Task Reset([FromForm] bool removeAttachments) { - var userId = this.User.FindFirstValue("sub"); + var userId = this.User.GetUserId(); if (await UserService.ResetUserAsync(userId, removeAttachments)) return Ok(); @@ -99,7 +99,7 @@ namespace Notesnook.API.Controllers [RequestTimeout(5 * 60 * 1000)] public async Task Delete([FromForm] DeleteAccountForm form) { - var userId = this.User.FindFirstValue("sub"); + var userId = this.User.GetUserId(); var jti = User.FindFirstValue("jti"); try { diff --git a/Notesnook.API/Extensions/ClaimsPrincipalExtensions.cs b/Notesnook.API/Extensions/ClaimsPrincipalExtensions.cs index 0cb0bec..9201fde 100644 --- a/Notesnook.API/Extensions/ClaimsPrincipalExtensions.cs +++ b/Notesnook.API/Extensions/ClaimsPrincipalExtensions.cs @@ -10,5 +10,8 @@ namespace System.Security.Claims private readonly static string[] SUBSCRIBED_CLAIMS = ["believer", "education", "essential", "pro", "legacy_pro"]; public static bool IsUserSubscribed(this ClaimsPrincipal user) => user.Claims.Any((c) => c.Type == "notesnook:status" && SUBSCRIBED_CLAIMS.Contains(c.Value)); + + public static string GetUserId(this ClaimsPrincipal user) + => user.FindFirstValue("sub") ?? throw new Exception("User ID not found in claims."); } } \ No newline at end of file diff --git a/Notesnook.API/Jobs/DeviceCleanupJob.cs b/Notesnook.API/Jobs/DeviceCleanupJob.cs index 1c3ddeb..eb849f7 100644 --- a/Notesnook.API/Jobs/DeviceCleanupJob.cs +++ b/Notesnook.API/Jobs/DeviceCleanupJob.cs @@ -7,7 +7,7 @@ namespace Notesnook.API.Jobs { public class DeviceCleanupJob : IJob { - public async Task Execute(IJobExecutionContext context) + public Task Execute(IJobExecutionContext context) { ParallelOptions parallelOptions = new() { @@ -59,6 +59,7 @@ namespace Notesnook.API.Jobs } } }); + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/Notesnook.API/Models/Announcement.cs b/Notesnook.API/Models/Announcement.cs index 7a343dd..a2494b1 100644 --- a/Notesnook.API/Models/Announcement.cs +++ b/Notesnook.API/Models/Announcement.cs @@ -40,7 +40,7 @@ namespace Notesnook.API.Models [JsonPropertyName("type")] [BsonElement("type")] - public string Type { get; set; } + public required string Type { get; set; } [JsonPropertyName("timestamp")] [BsonElement("timestamp")] @@ -48,7 +48,7 @@ namespace Notesnook.API.Models [JsonPropertyName("platforms")] [BsonElement("platforms")] - public string[] Platforms { get; set; } + public required string[] Platforms { get; set; } [JsonPropertyName("isActive")] [BsonElement("isActive")] @@ -56,7 +56,7 @@ namespace Notesnook.API.Models [JsonPropertyName("userTypes")] [BsonElement("userTypes")] - public string[] UserTypes { get; set; } + public required string[] UserTypes { get; set; } [JsonPropertyName("appVersion")] [BsonElement("appVersion")] @@ -64,63 +64,63 @@ namespace Notesnook.API.Models [JsonPropertyName("body")] [BsonElement("body")] - public BodyComponent[] Body { get; set; } + public required BodyComponent[] Body { get; set; } [JsonIgnore] [BsonElement("userIds")] - public string[] UserIds { get; set; } + public string[]? UserIds { get; set; } [Obsolete] [JsonPropertyName("title")] [DataMember(Name = "title")] [BsonElement("title")] - public string Title { get; set; } + public string? Title { get; set; } [Obsolete] [JsonPropertyName("description")] [BsonElement("description")] - public string Description { get; set; } + public string? Description { get; set; } [Obsolete] [JsonPropertyName("callToActions")] [BsonElement("callToActions")] - public CallToAction[] CallToActions { get; set; } + public CallToAction[]? CallToActions { get; set; } } public class BodyComponent { [JsonPropertyName("type")] [BsonElement("type")] - public string Type { get; set; } + public required string Type { get; set; } [JsonPropertyName("platforms")] [BsonElement("platforms")] - public string[] Platforms { get; set; } + public string[]? Platforms { get; set; } [JsonPropertyName("style")] [BsonElement("style")] - public Style Style { get; set; } + public Style? Style { get; set; } [JsonPropertyName("src")] [BsonElement("src")] - public string Src { get; set; } + public string? Src { get; set; } [JsonPropertyName("text")] [BsonElement("text")] - public string Text { get; set; } + public string? Text { get; set; } [JsonPropertyName("value")] [BsonElement("value")] - public string Value { get; set; } + public string? Value { get; set; } [JsonPropertyName("items")] [BsonElement("items")] - public BodyComponent[] Items { get; set; } + public BodyComponent[]? Items { get; set; } [JsonPropertyName("actions")] [BsonElement("actions")] - public CallToAction[] Actions { get; set; } + public required CallToAction[] Actions { get; set; } } public class Style @@ -135,25 +135,25 @@ namespace Notesnook.API.Models [JsonPropertyName("textAlign")] [BsonElement("textAlign")] - public string TextAlign { get; set; } + public string? TextAlign { get; set; } } public class CallToAction { [JsonPropertyName("type")] [BsonElement("type")] - public string Type { get; set; } + public required string Type { get; set; } [JsonPropertyName("platforms")] [BsonElement("platforms")] - public string[] Platforms { get; set; } + public string[]? Platforms { get; set; } [JsonPropertyName("data")] [BsonElement("data")] - public string Data { get; set; } + public string? Data { get; set; } [JsonPropertyName("title")] [BsonElement("title")] - public string Title { get; set; } + public string? Title { get; set; } } } \ No newline at end of file diff --git a/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs b/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs index 2fc9fd3..8929eda 100644 --- a/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs +++ b/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs @@ -5,9 +5,9 @@ namespace Notesnook.API.Models; public class CompleteMultipartUploadRequestWrapper { - public string Key { get; set; } - public List PartETags { get; set; } - public string UploadId { get; set; } + public required string Key { get; set; } + public required List PartETags { get; set; } + public required string UploadId { get; set; } public CompleteMultipartUploadRequest ToRequest() { diff --git a/Notesnook.API/Models/DeleteAccountForm.cs b/Notesnook.API/Models/DeleteAccountForm.cs index a504fda..17c7f89 100644 --- a/Notesnook.API/Models/DeleteAccountForm.cs +++ b/Notesnook.API/Models/DeleteAccountForm.cs @@ -5,7 +5,7 @@ namespace Notesnook.API.Models public class DeleteAccountForm { [Required] - public string Password + public required string Password { get; set; } diff --git a/Notesnook.API/Models/EncryptedData.cs b/Notesnook.API/Models/EncryptedData.cs index 64c4f7e..d26ad92 100644 --- a/Notesnook.API/Models/EncryptedData.cs +++ b/Notesnook.API/Models/EncryptedData.cs @@ -26,25 +26,19 @@ using System.Text.Json.Serialization; namespace Notesnook.API.Models { [MessagePack.MessagePackObject] - public class EncryptedData : IEncrypted + public class EncryptedData { [MessagePack.Key("iv")] [JsonPropertyName("iv")] [BsonElement("iv")] [DataMember(Name = "iv")] - public string IV - { - get; set; - } + public required string IV { get; set; } [MessagePack.Key("cipher")] [JsonPropertyName("cipher")] [BsonElement("cipher")] [DataMember(Name = "cipher")] - public string Cipher - { - get; set; - } + public required string Cipher { get; set; } [MessagePack.Key("length")] [JsonPropertyName("length")] @@ -56,9 +50,9 @@ namespace Notesnook.API.Models [JsonPropertyName("salt")] [BsonElement("salt")] [DataMember(Name = "salt")] - public string Salt { get; set; } + public required string Salt { get; set; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is EncryptedData encryptedData) { diff --git a/Notesnook.API/Models/InboxApiKey.cs b/Notesnook.API/Models/InboxApiKey.cs index 1809ad7..75acef6 100644 --- a/Notesnook.API/Models/InboxApiKey.cs +++ b/Notesnook.API/Models/InboxApiKey.cs @@ -37,16 +37,16 @@ namespace Notesnook.API.Models [BsonRepresentation(BsonType.ObjectId)] [JsonIgnore] [MessagePack.IgnoreMember] - public string Id { get; set; } + public string Id { get; set; } = string.Empty; [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; set; } [JsonPropertyName("key")] - public string Key { get; set; } + public string Key { get; set; } = string.Empty; [JsonPropertyName("dateCreated")] public long DateCreated { get; set; } diff --git a/Notesnook.API/Models/InboxSyncItem.cs b/Notesnook.API/Models/InboxSyncItem.cs index f830466..9948714 100644 --- a/Notesnook.API/Models/InboxSyncItem.cs +++ b/Notesnook.API/Models/InboxSyncItem.cs @@ -31,19 +31,13 @@ namespace Notesnook.API.Models [JsonPropertyName("key")] [MessagePack.Key("key")] [Required] - public EncryptedKey Key - { - get; set; - } + public required EncryptedKey Key { get; set; } [DataMember(Name = "salt")] [JsonPropertyName("salt")] [MessagePack.Key("salt")] [Required] - public string Salt - { - get; set; - } + public required string Salt { get; set; } } [MessagePack.MessagePackObject] @@ -53,19 +47,13 @@ namespace Notesnook.API.Models [JsonPropertyName("alg")] [MessagePack.Key("alg")] [Required] - public string Algorithm - { - get; set; - } + public required string Algorithm { get; set; } [DataMember(Name = "cipher")] [JsonPropertyName("cipher")] [MessagePack.Key("cipher")] [Required] - public string Cipher - { - get; set; - } + public required string Cipher { get; set; } [JsonPropertyName("length")] [DataMember(Name = "length")] diff --git a/Notesnook.API/Models/Monograph.cs b/Notesnook.API/Models/Monograph.cs index b646aaa..dc4e043 100644 --- a/Notesnook.API/Models/Monograph.cs +++ b/Notesnook.API/Models/Monograph.cs @@ -29,15 +29,9 @@ namespace Notesnook.API.Models [BsonId] [BsonIgnoreIfDefault] [BsonRepresentation(BsonType.ObjectId)] - public string Id - { - get; set; - } + public required string Id { get; set; } - public string ItemId - { - get; set; - } + public required string ItemId { get; set; } } public class Monograph @@ -50,23 +44,17 @@ namespace Notesnook.API.Models [DataMember(Name = "id")] [JsonPropertyName("id")] [MessagePack.Key("id")] - public string ItemId - { - get; set; - } + public string? ItemId { get; set; } [BsonId] [BsonIgnoreIfDefault] [BsonRepresentation(BsonType.ObjectId)] [JsonIgnore] [MessagePack.IgnoreMember] - public string Id - { - get; set; - } + public string Id { get; set; } = string.Empty; [JsonPropertyName("title")] - public string Title { get; set; } + public string? Title { get; set; } [JsonPropertyName("userId")] public string? UserId { get; set; } diff --git a/Notesnook.API/Models/MonographContent.cs b/Notesnook.API/Models/MonographContent.cs index aedcd14..65aaaae 100644 --- a/Notesnook.API/Models/MonographContent.cs +++ b/Notesnook.API/Models/MonographContent.cs @@ -28,8 +28,8 @@ namespace Notesnook.API.Models public class MonographContent { [JsonPropertyName("data")] - public string Data { get; set; } + public required string Data { get; set; } [JsonPropertyName("type")] - public string Type { get; set; } + public required string Type { get; set; } } } \ No newline at end of file diff --git a/Notesnook.API/Models/MonographMetadata.cs b/Notesnook.API/Models/MonographMetadata.cs index 85e700a..3deec16 100644 --- a/Notesnook.API/Models/MonographMetadata.cs +++ b/Notesnook.API/Models/MonographMetadata.cs @@ -35,7 +35,7 @@ namespace Notesnook.API.Models } [JsonPropertyName("title")] - public required string Title { get; set; } + public string? Title { get; set; } [JsonPropertyName("selfDestruct")] public bool SelfDestruct { get; set; } diff --git a/Notesnook.API/Models/MultipartUploadMeta.cs b/Notesnook.API/Models/MultipartUploadMeta.cs index 01ac2f7..82ba13e 100644 --- a/Notesnook.API/Models/MultipartUploadMeta.cs +++ b/Notesnook.API/Models/MultipartUploadMeta.cs @@ -17,11 +17,13 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ +using System; + namespace Notesnook.API.Models { public class MultipartUploadMeta { - public string UploadId { get; set; } - public string[] Parts { get; set; } + public string UploadId { get; set; } = string.Empty; + public string[] Parts { get; set; } = Array.Empty(); } } \ No newline at end of file diff --git a/Notesnook.API/Models/PartETagWrapper.cs b/Notesnook.API/Models/PartETagWrapper.cs index 46b4c9f..41caa09 100644 --- a/Notesnook.API/Models/PartETagWrapper.cs +++ b/Notesnook.API/Models/PartETagWrapper.cs @@ -3,5 +3,5 @@ public class PartETagWrapper { public int PartNumber { get; set; } - public string ETag { get; set; } + public string ETag { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Notesnook.API/Models/Responses/SignupResponse.cs b/Notesnook.API/Models/Responses/SignupResponse.cs index b5991a3..b5d6922 100644 --- a/Notesnook.API/Models/Responses/SignupResponse.cs +++ b/Notesnook.API/Models/Responses/SignupResponse.cs @@ -6,9 +6,9 @@ namespace Notesnook.API.Models.Responses public class SignupResponse : Response { [JsonPropertyName("userId")] - public string UserId { get; set; } + public string? UserId { get; set; } [JsonPropertyName("errors")] - public string[] Errors { get; set; } + public string[]? Errors { get; set; } } } diff --git a/Notesnook.API/Models/S3Options.cs b/Notesnook.API/Models/S3Options.cs index 791b7ef..9f42fda 100644 --- a/Notesnook.API/Models/S3Options.cs +++ b/Notesnook.API/Models/S3Options.cs @@ -21,9 +21,9 @@ namespace Notesnook.API.Models { public class S3Options { - public string ServiceUrl { get; set; } - public string Region { get; set; } - public string AccessKeyId { get; set; } - public string SecretAccessKey { get; set; } + public string ServiceUrl { get; set; } = string.Empty; + public string Region { get; set; } = string.Empty; + public string AccessKeyId { get; set; } = string.Empty; + public string SecretAccessKey { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Notesnook.API/Models/SyncItem.cs b/Notesnook.API/Models/SyncItem.cs index b4fdaef..95d97ed 100644 --- a/Notesnook.API/Models/SyncItem.cs +++ b/Notesnook.API/Models/SyncItem.cs @@ -53,20 +53,14 @@ namespace Notesnook.API.Models [DataMember(Name = "iv")] [MessagePack.Key("iv")] [Required] - public string IV - { - get; set; - } + public string IV { get; set; } = string.Empty; [JsonPropertyName("cipher")] [DataMember(Name = "cipher")] [MessagePack.Key("cipher")] [Required] - public string Cipher - { - get; set; - } + public string Cipher { get; set; } = string.Empty; [DataMember(Name = "id")] [JsonPropertyName("id")] @@ -108,10 +102,7 @@ namespace Notesnook.API.Models [DataMember(Name = "alg")] [MessagePack.Key("alg")] [Required] - public string Algorithm - { - get; set; - } + public string Algorithm { get; set; } = string.Empty; } public class SyncItemBsonSerializer : SerializerBase diff --git a/Notesnook.API/Models/UserSettings.cs b/Notesnook.API/Models/UserSettings.cs index 6f85501..e6cfaa6 100644 --- a/Notesnook.API/Models/UserSettings.cs +++ b/Notesnook.API/Models/UserSettings.cs @@ -29,23 +29,23 @@ namespace Notesnook.API.Models public long UpdatedAt { get; set; } } - public class UserSettings : IUserSettings + public class UserSettings { public UserSettings() { - this.Id = ObjectId.GenerateNewId().ToString(); + this.Id = ObjectId.GenerateNewId(); } - public string UserId { get; set; } + public required string UserId { get; set; } public long LastSynced { get; set; } - public string Salt { get; set; } + public required string Salt { get; set; } public EncryptedData? VaultKey { get; set; } public EncryptedData? AttachmentsKey { get; set; } public EncryptedData? MonographPasswordsKey { get; set; } public InboxKeys? InboxKeys { get; set; } - public Limit StorageLimit { get; set; } + public Limit? StorageLimit { get; set; } [BsonId] [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; set; } + public ObjectId Id { get; set; } } } diff --git a/Notesnook.API/Program.cs b/Notesnook.API/Program.cs index 89bd0ee..00cc5ed 100644 --- a/Notesnook.API/Program.cs +++ b/Notesnook.API/Program.cs @@ -50,7 +50,7 @@ namespace Notesnook.API { options.Limits.MaxRequestBodySize = long.MaxValue; options.ListenAnyIP(Servers.NotesnookAPI.Port); - if (Servers.NotesnookAPI.IsSecure) + if (Servers.NotesnookAPI.IsSecure && Servers.NotesnookAPI.SSLCertificate != null) { options.ListenAnyIP(443, listenerOptions => { diff --git a/Notesnook.API/Services/UserService.cs b/Notesnook.API/Services/UserService.cs index b587868..60f8da3 100644 --- a/Notesnook.API/Services/UserService.cs +++ b/Notesnook.API/Services/UserService.cs @@ -52,7 +52,7 @@ namespace Notesnook.API.Services public async Task CreateUserAsync() { SignupResponse response = await httpClient.ForwardAsync(this.HttpContextAccessor, $"{Servers.IdentityServer}/signup", HttpMethod.Post); - if (!response.Success || (response.Errors != null && response.Errors.Length > 0)) + if (!response.Success || (response.Errors != null && response.Errors.Length > 0) || response.UserId == null) { logger.LogError("Failed to sign up user: {Response}", JsonSerializer.Serialize(response)); if (response.Errors != null && response.Errors.Length > 0) @@ -216,7 +216,7 @@ namespace Notesnook.API.Services await S3Service.DeleteDirectoryAsync(userId); } - public async Task DeleteUserAsync(string userId, string jti, string password) + public async Task DeleteUserAsync(string userId, string? jti, string password) { logger.LogInformation("Deleting user account: {UserId}", userId); @@ -227,7 +227,7 @@ namespace Notesnook.API.Services await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage { - SendToAll = false, + SendToAll = jti == null, OriginTokenId = jti, UserId = userId, Message = new Message diff --git a/Notesnook.API/Startup.cs b/Notesnook.API/Startup.cs index 55b843e..599c7bc 100644 --- a/Notesnook.API/Startup.cs +++ b/Notesnook.API/Startup.cs @@ -119,8 +119,8 @@ namespace Notesnook.API policy.RequireAuthenticatedUser(); }); - options.DefaultPolicy = options.GetPolicy("Notesnook"); - }).AddSingleton(); ; + options.DefaultPolicy = options.GetPolicy("Notesnook") ?? throw new Exception("Notesnook policy not found"); + }).AddSingleton(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddOAuth2Introspection("introspection", options => @@ -138,13 +138,13 @@ namespace Notesnook.API options.Events.OnTokenValidated = (context) => { - if (long.TryParse(context.Principal.FindFirst("exp")?.Value, out long expiryTime)) + if (long.TryParse(context.Principal?.FindFirst("exp")?.Value, out long expiryTime)) { context.Properties.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(expiryTime); } context.Properties.AllowRefresh = true; context.Properties.IsPersistent = true; - context.HttpContext.User = context.Principal; + context.HttpContext.User = context.Principal ?? throw new Exception("No principal found in token."); return Task.CompletedTask; }; options.CacheKeyGenerator = (options, token) => (token + ":" + "reference_token").Sha256(); @@ -289,11 +289,6 @@ namespace Notesnook.API { endpoints.MapControllers(); endpoints.MapHealthChecks("/health"); - endpoints.MapHub("/hubs/sync", options => - { - options.CloseOnAuthenticationExpiration = false; - options.Transports = HttpTransportType.WebSockets; - }); endpoints.MapHub("/hubs/sync/v2", options => { options.CloseOnAuthenticationExpiration = false; @@ -307,7 +302,7 @@ namespace Notesnook.API { public static IServiceCollection AddMongoCollection(this IServiceCollection services, string collectionName, string database = "notesnook") { - services.AddKeyedSingleton(collectionName, (provider, key) => MongoDbContext.GetMongoCollection(provider.GetService(), database, collectionName)); + services.AddKeyedSingleton(collectionName, (provider, key) => MongoDbContext.GetMongoCollection(provider.GetRequiredService(), database, collectionName)); return services; } } diff --git a/Streetwriters.Common/Attributes/JsonInterfaceConverterAttribute.cs b/Streetwriters.Common/Attributes/JsonInterfaceConverterAttribute.cs deleted file mode 100644 index 3a7da81..0000000 --- a/Streetwriters.Common/Attributes/JsonInterfaceConverterAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* -This file is part of the Notesnook Sync Server project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the Affero GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -Affero GNU General Public License for more details. - -You should have received a copy of the Affero GNU General Public License -along with this program. If not, see . -*/ - -using System; -using System.Text.Json.Serialization; - -namespace Streetwriters.Common.Attributes -{ - [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)] - public class JsonInterfaceConverterAttribute : JsonConverterAttribute - { - public JsonInterfaceConverterAttribute(Type converterType) - : base(converterType) - { - } - } -} \ No newline at end of file diff --git a/Streetwriters.Common/Clients.cs b/Streetwriters.Common/Clients.cs index 94a1896..a237ed9 100644 --- a/Streetwriters.Common/Clients.cs +++ b/Streetwriters.Common/Clients.cs @@ -33,7 +33,7 @@ namespace Streetwriters.Common { Id = "notesnook", Name = "Notesnook", - SenderEmail = Constants.NOTESNOOK_SENDER_EMAIL, + SenderEmail = Constants.NOTESNOOK_SENDER_EMAIL ?? "noreply@notesnook.com", SenderName = "Notesnook", Type = ApplicationType.NOTESNOOK, AppId = ApplicationType.NOTESNOOK, @@ -58,14 +58,15 @@ namespace Streetwriters.Common { "notesnook", Notesnook } }; - public static Client FindClientById(string id) + public static Client? FindClientById(string? id) { - if (!IsValidClient(id)) return null; + if (string.IsNullOrEmpty(id) || !IsValidClient(id)) return null; return ClientsMap[id]; } - public static Client FindClientByAppId(ApplicationType appId) + public static Client? FindClientByAppId(ApplicationType? appId) { + if (appId is null) return null; switch (appId) { case ApplicationType.NOTESNOOK: diff --git a/Streetwriters.Common/Constants.cs b/Streetwriters.Common/Constants.cs index 2f87be0..09c8400 100644 --- a/Streetwriters.Common/Constants.cs +++ b/Streetwriters.Common/Constants.cs @@ -29,54 +29,54 @@ namespace Streetwriters.Common public static string INSTANCE_NAME => Environment.GetEnvironmentVariable("INSTANCE_NAME") ?? "default"; // S3 related - public static string S3_ACCESS_KEY => Environment.GetEnvironmentVariable("S3_ACCESS_KEY"); - public static string S3_ACCESS_KEY_ID => Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID"); - public static string S3_SERVICE_URL => Environment.GetEnvironmentVariable("S3_SERVICE_URL"); - public static string S3_REGION => Environment.GetEnvironmentVariable("S3_REGION"); - public static string S3_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_BUCKET_NAME"); - public static string S3_INTERNAL_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_INTERNAL_BUCKET_NAME"); - public static string S3_INTERNAL_SERVICE_URL => Environment.GetEnvironmentVariable("S3_INTERNAL_SERVICE_URL"); + public static string? S3_ACCESS_KEY => Environment.GetEnvironmentVariable("S3_ACCESS_KEY"); + public static string? S3_ACCESS_KEY_ID => Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID"); + public static string? S3_SERVICE_URL => Environment.GetEnvironmentVariable("S3_SERVICE_URL"); + public static string? S3_REGION => Environment.GetEnvironmentVariable("S3_REGION"); + public static string? S3_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_BUCKET_NAME"); + public static string? S3_INTERNAL_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_INTERNAL_BUCKET_NAME"); + public static string? S3_INTERNAL_SERVICE_URL => Environment.GetEnvironmentVariable("S3_INTERNAL_SERVICE_URL"); // SMTP settings - public static string SMTP_USERNAME => Environment.GetEnvironmentVariable("SMTP_USERNAME"); - public static string SMTP_PASSWORD => Environment.GetEnvironmentVariable("SMTP_PASSWORD"); - public static string SMTP_HOST => Environment.GetEnvironmentVariable("SMTP_HOST"); - public static string SMTP_PORT => Environment.GetEnvironmentVariable("SMTP_PORT"); - public static string SMTP_REPLYTO_EMAIL => Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL"); - public static string NOTESNOOK_SENDER_EMAIL => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL") ?? Environment.GetEnvironmentVariable("SMTP_USERNAME"); + public static string? SMTP_USERNAME => Environment.GetEnvironmentVariable("SMTP_USERNAME"); + public static string? SMTP_PASSWORD => Environment.GetEnvironmentVariable("SMTP_PASSWORD"); + public static string? SMTP_HOST => Environment.GetEnvironmentVariable("SMTP_HOST"); + public static string? SMTP_PORT => Environment.GetEnvironmentVariable("SMTP_PORT"); + public static string? SMTP_REPLYTO_EMAIL => Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL"); + public static string? NOTESNOOK_SENDER_EMAIL => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL") ?? Environment.GetEnvironmentVariable("SMTP_USERNAME"); - public static string NOTESNOOK_APP_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_APP_HOST"); - public static string NOTESNOOK_API_SECRET => Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET"); + public static string? NOTESNOOK_APP_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_APP_HOST"); + public static string NOTESNOOK_API_SECRET => Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET") ?? throw new InvalidOperationException("NOTESNOOK_API_SECRET is required"); // MessageBird is used for SMS sending - public static string TWILIO_ACCOUNT_SID => Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID"); - public static string TWILIO_AUTH_TOKEN => Environment.GetEnvironmentVariable("TWILIO_AUTH_TOKEN"); - public static string TWILIO_SERVICE_SID => Environment.GetEnvironmentVariable("TWILIO_SERVICE_SID"); + public static string? TWILIO_ACCOUNT_SID => Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID"); + public static string? TWILIO_AUTH_TOKEN => Environment.GetEnvironmentVariable("TWILIO_AUTH_TOKEN"); + public static string? TWILIO_SERVICE_SID => Environment.GetEnvironmentVariable("TWILIO_SERVICE_SID"); // Server discovery public static int NOTESNOOK_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_PORT") ?? "80"); - public static string NOTESNOOK_SERVER_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_HOST"); - public static string NOTESNOOK_CERT_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_PATH"); - public static string NOTESNOOK_CERT_KEY_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_KEY_PATH"); + public static string? NOTESNOOK_SERVER_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_HOST"); + public static string? NOTESNOOK_CERT_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_PATH"); + public static string? NOTESNOOK_CERT_KEY_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_KEY_PATH"); public static int IDENTITY_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("IDENTITY_SERVER_PORT") ?? "80"); - public static string IDENTITY_SERVER_HOST => Environment.GetEnvironmentVariable("IDENTITY_SERVER_HOST"); - public static Uri IDENTITY_SERVER_URL => new(Environment.GetEnvironmentVariable("IDENTITY_SERVER_URL")); - public static string IDENTITY_CERT_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_PATH"); - public static string IDENTITY_CERT_KEY_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_KEY_PATH"); + public static string? IDENTITY_SERVER_HOST => Environment.GetEnvironmentVariable("IDENTITY_SERVER_HOST"); + public static Uri? IDENTITY_SERVER_URL => Environment.GetEnvironmentVariable("IDENTITY_SERVER_URL") is string url ? new Uri(url) : null; + public static string? IDENTITY_CERT_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_PATH"); + public static string? IDENTITY_CERT_KEY_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_KEY_PATH"); public static int SSE_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SSE_SERVER_PORT") ?? "80"); - public static string SSE_SERVER_HOST => Environment.GetEnvironmentVariable("SSE_SERVER_HOST"); - public static string SSE_CERT_PATH => Environment.GetEnvironmentVariable("SSE_CERT_PATH"); - public static string SSE_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SSE_CERT_KEY_PATH"); + public static string? SSE_SERVER_HOST => Environment.GetEnvironmentVariable("SSE_SERVER_HOST"); + public static string? SSE_CERT_PATH => Environment.GetEnvironmentVariable("SSE_CERT_PATH"); + public static string? SSE_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SSE_CERT_KEY_PATH"); // internal - public static string WEBRISK_API_URI => Environment.GetEnvironmentVariable("WEBRISK_API_URI"); - public static string MONGODB_CONNECTION_STRING => Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING"); - public static string MONGODB_DATABASE_NAME => Environment.GetEnvironmentVariable("MONGODB_DATABASE_NAME"); + public static string? WEBRISK_API_URI => Environment.GetEnvironmentVariable("WEBRISK_API_URI"); + public static string MONGODB_CONNECTION_STRING => Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING") ?? throw new ArgumentNullException("MONGODB_CONNECTION_STRING environment variable is not set"); + public static string MONGODB_DATABASE_NAME => Environment.GetEnvironmentVariable("MONGODB_DATABASE_NAME") ?? throw new ArgumentNullException("MONGODB_DATABASE_NAME environment variable is not set"); public static int SUBSCRIPTIONS_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_PORT") ?? "80"); - public static string SUBSCRIPTIONS_SERVER_HOST => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_HOST"); - public static string SUBSCRIPTIONS_CERT_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_PATH"); - public static string SUBSCRIPTIONS_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_KEY_PATH"); + public static string? SUBSCRIPTIONS_SERVER_HOST => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_HOST"); + public static string? SUBSCRIPTIONS_CERT_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_PATH"); + public static string? SUBSCRIPTIONS_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_KEY_PATH"); public static string[] NOTESNOOK_CORS_ORIGINS => Environment.GetEnvironmentVariable("NOTESNOOK_CORS")?.Split(",") ?? new string[] { }; } } \ No newline at end of file diff --git a/Streetwriters.Common/Converters/InterfaceConverter.cs b/Streetwriters.Common/Converters/InterfaceConverter.cs deleted file mode 100644 index 00ee621..0000000 --- a/Streetwriters.Common/Converters/InterfaceConverter.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* -This file is part of the Notesnook Sync Server project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the Affero GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -Affero GNU General Public License for more details. - -You should have received a copy of the Affero GNU General Public License -along with this program. If not, see . -*/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Streetwriters.Common.Converters -{ - /// - /// Converts simple interface into an object (assumes that there is only one class of TInterface) - /// - /// Interface type - /// Class type - public class InterfaceConverter : JsonConverter where TClass : TInterface - { - public override TInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return JsonSerializer.Deserialize(ref reader, options); - } - - public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options) - { - switch (value) - { - case null: - JsonSerializer.Serialize(writer, null, options); - break; - default: - { - var type = value.GetType(); - JsonSerializer.Serialize(writer, value, type, options); - break; - } - } - } - } -} \ No newline at end of file diff --git a/Streetwriters.Common/Extensions/AppBuilderExtensions.cs b/Streetwriters.Common/Extensions/AppBuilderExtensions.cs index b562978..19e10a4 100644 --- a/Streetwriters.Common/Extensions/AppBuilderExtensions.cs +++ b/Streetwriters.Common/Extensions/AppBuilderExtensions.cs @@ -42,7 +42,7 @@ namespace Streetwriters.Common.Extensions var data = new Dictionary { { "version", Constants.COMPATIBILITY_VERSION }, - { "id", server.Id }, + { "id", server.Id ?? "unknown" }, { "instance", Constants.INSTANCE_NAME } }; await context.Response.WriteAsync(JsonSerializer.Serialize(data)); @@ -70,12 +70,12 @@ namespace Streetwriters.Common.Extensions return app; } - public static T GetService(this IApplicationBuilder app) + public static T GetService(this IApplicationBuilder app) where T : notnull { return app.ApplicationServices.GetRequiredService(); } - public static T GetScopedService(this IApplicationBuilder app) + public static T GetScopedService(this IApplicationBuilder app) where T : notnull { using (var scope = app.ApplicationServices.CreateScope()) { diff --git a/Streetwriters.Common/Extensions/HttpClientExtensions.cs b/Streetwriters.Common/Extensions/HttpClientExtensions.cs index c293eee..27993e0 100644 --- a/Streetwriters.Common/Extensions/HttpClientExtensions.cs +++ b/Streetwriters.Common/Extensions/HttpClientExtensions.cs @@ -17,6 +17,7 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ +using System; using System.Linq; using System.Net.Http; using System.Net.Http.Json; @@ -28,7 +29,7 @@ namespace Streetwriters.Common.Extensions { public static class HttpClientExtensions { - public static async Task SendRequestAsync(this HttpClient httpClient, string url, IHeaderDictionary headers, HttpMethod method, HttpContent content = null) where T : IResponse, new() + public static async Task SendRequestAsync(this HttpClient httpClient, string url, IHeaderDictionary? headers, HttpMethod method, HttpContent? content = null) where T : IResponse, new() { var request = new HttpRequestMessage(method, url); @@ -51,22 +52,23 @@ namespace Streetwriters.Common.Extensions } var response = await httpClient.SendAsync(request); - if (response.Content.Headers.ContentLength > 0 && response.Content.Headers.ContentType.ToString().Contains("application/json")) + if (response.Content.Headers.ContentLength > 0 && response.Content.Headers.ContentType?.ToString()?.Contains("application/json") == true) { var res = await response.Content.ReadFromJsonAsync(); - res.Success = response.IsSuccessStatusCode; - res.StatusCode = (int)response.StatusCode; - return res; - } - else - { - return new T { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Content = response.Content }; + if (res != null) + { + res.Success = response.IsSuccessStatusCode; + res.StatusCode = (int)response.StatusCode; + return res; + } } + + return new T { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Content = response.Content }; } public static Task ForwardAsync(this HttpClient httpClient, IHttpContextAccessor accessor, string url, HttpMethod method) where T : IResponse, new() { - var httpContext = accessor.HttpContext; + var httpContext = accessor.HttpContext ?? throw new InvalidOperationException("HttpContext is not available"); var content = new StreamContent(httpContext.Request.BodyReader.AsStream()); return httpClient.SendRequestAsync(url, httpContext.Request.Headers, method, content); } diff --git a/Streetwriters.Common/Extensions/ServiceCollectionServiceExtensions.cs b/Streetwriters.Common/Extensions/ServiceCollectionServiceExtensions.cs index a47390a..be29d13 100644 --- a/Streetwriters.Common/Extensions/ServiceCollectionServiceExtensions.cs +++ b/Streetwriters.Common/Extensions/ServiceCollectionServiceExtensions.cs @@ -27,7 +27,7 @@ namespace Streetwriters.Common.Extensions { public static IServiceCollection AddRepository(this IServiceCollection services, string collectionName, string database) where T : class { - services.AddSingleton((provider) => MongoDbContext.GetMongoCollection(provider.GetService(), database, collectionName)); + services.AddSingleton((provider) => MongoDbContext.GetMongoCollection(provider.GetRequiredService(), database, collectionName)); services.AddScoped>(); return services; } diff --git a/Streetwriters.Common/Interfaces/IClient.cs b/Streetwriters.Common/Interfaces/IClient.cs index f5cfade..7e5a01c 100644 --- a/Streetwriters.Common/Interfaces/IClient.cs +++ b/Streetwriters.Common/Interfaces/IClient.cs @@ -33,6 +33,6 @@ namespace Streetwriters.Common.Interfaces string SenderName { get; set; } string EmailConfirmedRedirectURL { get; } string AccountRecoveryRedirectURL { get; } - Func OnEmailConfirmed { get; set; } + Func? OnEmailConfirmed { get; set; } } } diff --git a/Streetwriters.Common/Interfaces/IDocument.cs b/Streetwriters.Common/Interfaces/IDocument.cs index 9773212..23bf5e8 100644 --- a/Streetwriters.Common/Interfaces/IDocument.cs +++ b/Streetwriters.Common/Interfaces/IDocument.cs @@ -17,11 +17,13 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ +using MongoDB.Bson; + namespace Streetwriters.Common.Interfaces { public interface IDocument { - string Id + ObjectId Id { get; set; } diff --git a/Streetwriters.Common/Interfaces/IEmailSender.cs b/Streetwriters.Common/Interfaces/IEmailSender.cs index ab6cbcd..570d0c6 100644 --- a/Streetwriters.Common/Interfaces/IEmailSender.cs +++ b/Streetwriters.Common/Interfaces/IEmailSender.cs @@ -12,8 +12,8 @@ namespace Streetwriters.Common.Interfaces string email, EmailTemplate template, IClient client, - GnuPGContext gpgContext = null, - Dictionary attachments = null + GnuPGContext? gpgContext = null, + Dictionary? attachments = null ); } } diff --git a/Streetwriters.Common/Interfaces/IOffer.cs b/Streetwriters.Common/Interfaces/IOffer.cs deleted file mode 100644 index a43a301..0000000 --- a/Streetwriters.Common/Interfaces/IOffer.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* -This file is part of the Notesnook Sync Server project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the Affero GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -Affero GNU General Public License for more details. - -You should have received a copy of the Affero GNU General Public License -along with this program. If not, see . -*/ - -using System.Collections.Generic; -using Streetwriters.Common.Enums; -using Streetwriters.Common.Models; - -namespace Streetwriters.Common.Interfaces -{ - public interface IOffer : IDocument - { - ApplicationType AppId { get; set; } - string PromoCode { get; set; } - PromoCode[] Codes { get; set; } - } -} diff --git a/Streetwriters.Common/Interfaces/IResponse.cs b/Streetwriters.Common/Interfaces/IResponse.cs index bd8ca70..2351e47 100644 --- a/Streetwriters.Common/Interfaces/IResponse.cs +++ b/Streetwriters.Common/Interfaces/IResponse.cs @@ -25,6 +25,6 @@ namespace Streetwriters.Common.Interfaces { bool Success { get; set; } int StatusCode { get; set; } - HttpContent Content { get; set; } + HttpContent? Content { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs b/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs index a7b2881..ee50ac2 100644 --- a/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs +++ b/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs @@ -8,7 +8,7 @@ namespace Streetwriters.Common.Interfaces public interface IUserSubscriptionService { [WampProcedure("co.streetwriters.subscriptions.subscriptions.get_user_subscription")] - Task GetUserSubscriptionAsync(string clientId, string userId); + Task GetUserSubscriptionAsync(string clientId, string userId); Subscription TransformUserSubscription(Subscription subscription); } } \ No newline at end of file diff --git a/Streetwriters.Common/Messages/CreateSubscriptionMessage.cs b/Streetwriters.Common/Messages/CreateSubscriptionMessage.cs index bec8b7c..d70f376 100644 --- a/Streetwriters.Common/Messages/CreateSubscriptionMessage.cs +++ b/Streetwriters.Common/Messages/CreateSubscriptionMessage.cs @@ -28,7 +28,7 @@ namespace Streetwriters.Common.Messages public class CreateSubscriptionMessage { [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("provider")] public SubscriptionProvider Provider { get; set; } @@ -46,19 +46,19 @@ namespace Streetwriters.Common.Messages public long ExpiryTime { get; set; } [JsonPropertyName("orderId")] - public string OrderId { get; set; } + public string? OrderId { get; set; } [JsonPropertyName("updateURL")] - public string UpdateURL { get; set; } + public string? UpdateURL { get; set; } [JsonPropertyName("cancelURL")] - public string CancelURL { get; set; } + public string? CancelURL { get; set; } [JsonPropertyName("subscriptionId")] - public string SubscriptionId { get; set; } + public string? SubscriptionId { get; set; } [JsonPropertyName("productId")] - public string ProductId { get; set; } + public string? ProductId { get; set; } [JsonPropertyName("extend")] public bool Extend { get; set; } diff --git a/Streetwriters.Common/Messages/CreateSubscriptionMessageV2.cs b/Streetwriters.Common/Messages/CreateSubscriptionMessageV2.cs index 830faa9..9a2ed7f 100644 --- a/Streetwriters.Common/Messages/CreateSubscriptionMessageV2.cs +++ b/Streetwriters.Common/Messages/CreateSubscriptionMessageV2.cs @@ -28,7 +28,7 @@ namespace Streetwriters.Common.Messages public class CreateSubscriptionMessageV2 { [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("provider")] public SubscriptionProvider Provider { get; set; } @@ -49,13 +49,13 @@ namespace Streetwriters.Common.Messages public long ExpiryTime { get; set; } [JsonPropertyName("orderId")] - public string OrderId { get; set; } + public string? OrderId { get; set; } [JsonPropertyName("subscriptionId")] - public string SubscriptionId { get; set; } + public string? SubscriptionId { get; set; } [JsonPropertyName("productId")] - public string ProductId { get; set; } + public string? ProductId { get; set; } [JsonPropertyName("timestamp")] public long Timestamp { get; set; } diff --git a/Streetwriters.Common/Messages/DeleteSubscriptionMessage.cs b/Streetwriters.Common/Messages/DeleteSubscriptionMessage.cs index db605ff..5179017 100644 --- a/Streetwriters.Common/Messages/DeleteSubscriptionMessage.cs +++ b/Streetwriters.Common/Messages/DeleteSubscriptionMessage.cs @@ -27,7 +27,7 @@ namespace Streetwriters.Common.Messages public class DeleteSubscriptionMessage { [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("appId")] public ApplicationType AppId { get; set; } diff --git a/Streetwriters.Common/Messages/DeleteUserMessage.cs b/Streetwriters.Common/Messages/DeleteUserMessage.cs index a8c59d9..4a6e00a 100644 --- a/Streetwriters.Common/Messages/DeleteUserMessage.cs +++ b/Streetwriters.Common/Messages/DeleteUserMessage.cs @@ -27,6 +27,6 @@ namespace Streetwriters.Common.Messages public class DeleteUserMessage { [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Common/Messages/SendSSEMessage.cs b/Streetwriters.Common/Messages/SendSSEMessage.cs index ec02268..89d58e7 100644 --- a/Streetwriters.Common/Messages/SendSSEMessage.cs +++ b/Streetwriters.Common/Messages/SendSSEMessage.cs @@ -26,10 +26,10 @@ namespace Streetwriters.Common.Messages public class Message { [JsonPropertyName("type")] - public string Type { get; set; } + public required string Type { get; set; } [JsonPropertyName("data")] - public string Data { get; set; } + public string? Data { get; set; } } public class SendSSEMessage { @@ -37,10 +37,10 @@ namespace Streetwriters.Common.Messages public bool SendToAll { get; set; } [JsonPropertyName("userId")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("message")] - public Message Message { get; set; } + public required Message Message { get; set; } [JsonPropertyName("originTokenId")] public string? OriginTokenId { get; set; } diff --git a/Streetwriters.Common/Models/Client.cs b/Streetwriters.Common/Models/Client.cs index 75fa4ec..5ecb720 100644 --- a/Streetwriters.Common/Models/Client.cs +++ b/Streetwriters.Common/Models/Client.cs @@ -31,15 +31,15 @@ namespace Streetwriters.Common.Models { public class Client : IClient { - public string Id { get; set; } - public string Name { get; set; } + public required string Id { get; set; } + public required string Name { get; set; } public ApplicationType Type { get; set; } public ApplicationType AppId { get; set; } - public string SenderEmail { get; set; } - public string SenderName { get; set; } - public string EmailConfirmedRedirectURL { get; set; } - public string AccountRecoveryRedirectURL { get; set; } + public required string SenderEmail { get; set; } + public required string SenderName { get; set; } + public required string EmailConfirmedRedirectURL { get; set; } + public required string AccountRecoveryRedirectURL { get; set; } - public Func OnEmailConfirmed { get; set; } + public Func? OnEmailConfirmed { get; set; } } } diff --git a/Streetwriters.Common/Models/EmailTemplate.cs b/Streetwriters.Common/Models/EmailTemplate.cs index 76bd162..93f59e9 100644 --- a/Streetwriters.Common/Models/EmailTemplate.cs +++ b/Streetwriters.Common/Models/EmailTemplate.cs @@ -3,9 +3,9 @@ namespace Streetwriters.Common.Models public class EmailTemplate { public int? Id { get; set; } - public object Data { get; set; } - public string Subject { get; set; } - public string Html { get; set; } - public string Text { get; set; } + public object? Data { get; set; } + public required string Subject { get; set; } + public required string Html { get; set; } + public required string Text { get; set; } } } diff --git a/Streetwriters.Common/Models/GetCustomerResponse.cs b/Streetwriters.Common/Models/GetCustomerResponse.cs index 59203e4..bbcbb8e 100644 --- a/Streetwriters.Common/Models/GetCustomerResponse.cs +++ b/Streetwriters.Common/Models/GetCustomerResponse.cs @@ -10,12 +10,12 @@ namespace Streetwriters.Common.Models public partial class GetCustomerResponse : PaddleResponse { [JsonPropertyName("data")] - public PaddleCustomer Customer { get; set; } + public PaddleCustomer? Customer { get; set; } } public class PaddleCustomer { [JsonPropertyName("email")] - public string Email { get; set; } + public string? Email { get; set; } } } diff --git a/Streetwriters.Common/Models/GetSubscriptionResponse.cs b/Streetwriters.Common/Models/GetSubscriptionResponse.cs index c431915..d1e217a 100644 --- a/Streetwriters.Common/Models/GetSubscriptionResponse.cs +++ b/Streetwriters.Common/Models/GetSubscriptionResponse.cs @@ -10,7 +10,7 @@ namespace Streetwriters.Common.Models public partial class GetSubscriptionResponse : PaddleResponse { [JsonPropertyName("data")] - public Data Data { get; set; } + public Data? Data { get; set; } } public partial class Data @@ -22,7 +22,7 @@ namespace Streetwriters.Common.Models // public string Status { get; set; } [JsonPropertyName("customer_id")] - public string CustomerId { get; set; } + public string? CustomerId { get; set; } // [JsonPropertyName("address_id")] // public string AddressId { get; set; } @@ -64,7 +64,7 @@ namespace Streetwriters.Common.Models // public CurrentBillingPeriod CurrentBillingPeriod { get; set; } [JsonPropertyName("billing_cycle")] - public BillingCycle BillingCycle { get; set; } + public BillingCycle? BillingCycle { get; set; } // [JsonPropertyName("scheduled_change")] // public object ScheduledChange { get; set; } @@ -76,7 +76,7 @@ namespace Streetwriters.Common.Models // public object CustomData { get; set; } [JsonPropertyName("management_urls")] - public ManagementUrls ManagementUrls { get; set; } + public ManagementUrls? ManagementUrls { get; set; } // [JsonPropertyName("discount")] // public object Discount { get; set; } @@ -91,7 +91,7 @@ namespace Streetwriters.Common.Models public long Frequency { get; set; } [JsonPropertyName("interval")] - public string Interval { get; set; } + public string? Interval { get; set; } } // public partial class CurrentBillingPeriod @@ -206,9 +206,9 @@ namespace Streetwriters.Common.Models public partial class ManagementUrls { [JsonPropertyName("update_payment_method")] - public Uri UpdatePaymentMethod { get; set; } + public Uri? UpdatePaymentMethod { get; set; } [JsonPropertyName("cancel")] - public Uri Cancel { get; set; } + public Uri? Cancel { get; set; } } } diff --git a/Streetwriters.Common/Models/GetTransactionInvoiceResponse.cs b/Streetwriters.Common/Models/GetTransactionInvoiceResponse.cs index 9edd9d4..1244008 100644 --- a/Streetwriters.Common/Models/GetTransactionInvoiceResponse.cs +++ b/Streetwriters.Common/Models/GetTransactionInvoiceResponse.cs @@ -10,12 +10,12 @@ namespace Streetwriters.Common.Models public class GetTransactionInvoiceResponse : PaddleResponse { [JsonPropertyName("data")] - public Invoice Invoice { get; set; } + public Invoice? Invoice { get; set; } } public partial class Invoice { [JsonPropertyName("url")] - public string Url { get; set; } + public string? Url { get; set; } } } diff --git a/Streetwriters.Common/Models/GetTransactionResponse.cs b/Streetwriters.Common/Models/GetTransactionResponse.cs index 5ef8102..306ce35 100644 --- a/Streetwriters.Common/Models/GetTransactionResponse.cs +++ b/Streetwriters.Common/Models/GetTransactionResponse.cs @@ -10,6 +10,6 @@ namespace Streetwriters.Common.Models public partial class GetTransactionResponse : PaddleResponse { [JsonPropertyName("data")] - public TransactionV2 Transaction { get; set; } + public TransactionV2? Transaction { get; set; } } } diff --git a/Streetwriters.Common/Models/GiftCard.cs b/Streetwriters.Common/Models/GiftCard.cs index 108e35e..c24bbf0 100644 --- a/Streetwriters.Common/Models/GiftCard.cs +++ b/Streetwriters.Common/Models/GiftCard.cs @@ -9,14 +9,14 @@ namespace Streetwriters.Common.Models { public GiftCard() { - Id = ObjectId.GenerateNewId().ToString(); + Id = ObjectId.GenerateNewId(); } - public string Code { get; set; } - public string OrderId { get; set; } - public string OrderIdType { get; set; } - public string ProductId { get; set; } - public string RedeemedBy { get; set; } + public required string Code { get; set; } + public required string OrderId { get; set; } + public required string OrderIdType { get; set; } + public required string ProductId { get; set; } + public string? RedeemedBy { get; set; } public long RedeemedAt { get; set; } public long Timestamp { get; set; } public long Term { get; set; } @@ -24,6 +24,6 @@ namespace Streetwriters.Common.Models [BsonId] [BsonRepresentation(BsonType.ObjectId)] [JsonIgnore] - public string Id { get; set; } + public ObjectId Id { get; set; } } } diff --git a/Streetwriters.Common/Models/ListPaymentsResponse.cs b/Streetwriters.Common/Models/ListPaymentsResponse.cs index 1703a84..e27e649 100644 --- a/Streetwriters.Common/Models/ListPaymentsResponse.cs +++ b/Streetwriters.Common/Models/ListPaymentsResponse.cs @@ -9,7 +9,7 @@ namespace Streetwriters.Common.Models public bool Success { get; set; } [JsonPropertyName("response")] - public Payment[] Payments { get; set; } + public Payment[]? Payments { get; set; } } public partial class Payment @@ -24,10 +24,10 @@ namespace Streetwriters.Common.Models public double Amount { get; set; } [JsonPropertyName("currency")] - public string Currency { get; set; } + public string? Currency { get; set; } [JsonPropertyName("payout_date")] - public string PayoutDate { get; set; } + public string? PayoutDate { get; set; } [JsonPropertyName("is_paid")] public short IsPaid { get; set; } @@ -36,6 +36,6 @@ namespace Streetwriters.Common.Models public bool IsOneOffCharge { get; set; } [JsonPropertyName("receipt_url")] - public string ReceiptUrl { get; set; } + public string? ReceiptUrl { get; set; } } } diff --git a/Streetwriters.Common/Models/ListTransactionsResponse.cs b/Streetwriters.Common/Models/ListTransactionsResponse.cs index 9379eb7..62da3b8 100644 --- a/Streetwriters.Common/Models/ListTransactionsResponse.cs +++ b/Streetwriters.Common/Models/ListTransactionsResponse.cs @@ -9,31 +9,31 @@ namespace Streetwriters.Common.Models public bool Success { get; set; } [JsonPropertyName("response")] - public Transaction[] Transactions { get; set; } + public Transaction[]? Transactions { get; set; } } public partial class Transaction { [JsonPropertyName("order_id")] - public string OrderId { get; set; } + public string? OrderId { get; set; } [JsonPropertyName("checkout_id")] - public string CheckoutId { get; set; } + public string? CheckoutId { get; set; } [JsonPropertyName("amount")] - public string Amount { get; set; } + public string? Amount { get; set; } [JsonPropertyName("currency")] - public string Currency { get; set; } + public string? Currency { get; set; } [JsonPropertyName("status")] - public string Status { get; set; } + public string? Status { get; set; } [JsonPropertyName("created_at")] - public string CreatedAt { get; set; } + public string? CreatedAt { get; set; } [JsonPropertyName("passthrough")] - public object Passthrough { get; set; } + public object? Passthrough { get; set; } [JsonPropertyName("product_id")] public long ProductId { get; set; } @@ -45,13 +45,13 @@ namespace Streetwriters.Common.Models public bool IsOneOff { get; set; } [JsonPropertyName("subscription")] - public PaddleSubscription Subscription { get; set; } + public PaddleSubscription? Subscription { get; set; } [JsonPropertyName("user")] - public PaddleTransactionUser User { get; set; } + public PaddleTransactionUser? User { get; set; } [JsonPropertyName("receipt_url")] - public string ReceiptUrl { get; set; } + public string? ReceiptUrl { get; set; } } public partial class PaddleSubscription @@ -60,7 +60,7 @@ namespace Streetwriters.Common.Models public long SubscriptionId { get; set; } [JsonPropertyName("status")] - public string Status { get; set; } + public string? Status { get; set; } } public partial class PaddleTransactionUser @@ -69,7 +69,7 @@ namespace Streetwriters.Common.Models public long UserId { get; set; } [JsonPropertyName("email")] - public string Email { get; set; } + public string? Email { get; set; } [JsonPropertyName("marketing_consent")] public bool MarketingConsent { get; set; } diff --git a/Streetwriters.Common/Models/ListTransactionsResponseV2.cs b/Streetwriters.Common/Models/ListTransactionsResponseV2.cs index 647ccd0..5c57e7e 100644 --- a/Streetwriters.Common/Models/ListTransactionsResponseV2.cs +++ b/Streetwriters.Common/Models/ListTransactionsResponseV2.cs @@ -10,19 +10,19 @@ namespace Streetwriters.Common.Models public partial class ListTransactionsResponseV2 : PaddleResponse { [JsonPropertyName("data")] - public TransactionV2[] Transactions { get; set; } + public TransactionV2[]? Transactions { get; set; } } public partial class TransactionV2 { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("status")] - public string Status { get; set; } + public string? Status { get; set; } [JsonPropertyName("customer_id")] - public string CustomerId { get; set; } + public string? CustomerId { get; set; } // [JsonPropertyName("address_id")] // public string AddressId { get; set; } @@ -31,10 +31,10 @@ namespace Streetwriters.Common.Models // public object BusinessId { get; set; } [JsonPropertyName("custom_data")] - public Dictionary CustomData { get; set; } + public Dictionary? CustomData { get; set; } [JsonPropertyName("origin")] - public string Origin { get; set; } + public string? Origin { get; set; } // [JsonPropertyName("collection_mode")] // public string CollectionMode { get; set; } @@ -49,10 +49,10 @@ namespace Streetwriters.Common.Models // public string InvoiceNumber { get; set; } [JsonPropertyName("billing_details")] - public BillingDetails BillingDetails { get; set; } + public BillingDetails? BillingDetails { get; set; } [JsonPropertyName("billing_period")] - public BillingPeriod BillingPeriod { get; set; } + public BillingPeriod? BillingPeriod { get; set; } // [JsonPropertyName("currency_code")] // public string CurrencyCode { get; set; } @@ -70,10 +70,10 @@ namespace Streetwriters.Common.Models public DateTimeOffset? BilledAt { get; set; } [JsonPropertyName("items")] - public Item[] Items { get; set; } + public Item[]? Items { get; set; } [JsonPropertyName("details")] - public Details Details { get; set; } + public Details? Details { get; set; } // [JsonPropertyName("payments")] // public Payment[] Payments { get; set; } @@ -88,7 +88,7 @@ namespace Streetwriters.Common.Models // public bool EnableCheckout { get; set; } [JsonPropertyName("payment_terms")] - public PaymentTerms PaymentTerms { get; set; } + public PaymentTerms? PaymentTerms { get; set; } // [JsonPropertyName("purchase_order_number")] // public string PurchaseOrderNumber { get; set; } @@ -100,7 +100,7 @@ namespace Streetwriters.Common.Models public partial class PaymentTerms { [JsonPropertyName("interval")] - public string Interval { get; set; } + public string? Interval { get; set; } [JsonPropertyName("frequency")] public long Frequency { get; set; } @@ -127,7 +127,7 @@ namespace Streetwriters.Common.Models // public TaxRatesUsed[] TaxRatesUsed { get; set; } [JsonPropertyName("totals")] - public Totals Totals { get; set; } + public Totals? Totals { get; set; } // [JsonPropertyName("adjusted_totals")] // public AdjustedTotals AdjustedTotals { get; set; } @@ -139,7 +139,7 @@ namespace Streetwriters.Common.Models // public AdjustedTotals AdjustedPayoutTotals { get; set; } [JsonPropertyName("line_items")] - public LineItem[] LineItems { get; set; } + public LineItem[]? LineItems { get; set; } } public partial class Totals @@ -175,7 +175,7 @@ namespace Streetwriters.Common.Models // public object Earnings { get; set; } [JsonPropertyName("currency_code")] - public string CurrencyCode { get; set; } + public string? CurrencyCode { get; set; } } // public partial class AdjustedTotals // { @@ -225,10 +225,10 @@ namespace Streetwriters.Common.Models public partial class LineItem { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("price_id")] - public string PriceId { get; set; } + public string? PriceId { get; set; } // [JsonPropertyName("quantity")] // public long Quantity { get; set; } @@ -247,7 +247,7 @@ namespace Streetwriters.Common.Models [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("proration")] - public Proration Proration { get; set; } + public Proration? Proration { get; set; } } // public partial class Product @@ -322,7 +322,7 @@ namespace Streetwriters.Common.Models public partial class Proration { [JsonPropertyName("billing_period")] - public BillingPeriod BillingPeriod { get; set; } + public BillingPeriod? BillingPeriod { get; set; } } // public partial class Totals @@ -356,20 +356,20 @@ namespace Streetwriters.Common.Models public partial class Item { [JsonPropertyName("price")] - public Price Price { get; set; } + public Price? Price { get; set; } [JsonPropertyName("quantity")] public long Quantity { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("proration")] - public Proration Proration { get; set; } + public Proration? Proration { get; set; } } public partial class Price { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } // [JsonPropertyName("description")] // public string Description { get; set; } @@ -378,7 +378,7 @@ namespace Streetwriters.Common.Models // public TypeEnum Type { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } // [JsonPropertyName("product_id")] // public string ProductId { get; set; } @@ -500,7 +500,7 @@ namespace Streetwriters.Common.Models public long PerPage { get; set; } [JsonPropertyName("next")] - public Uri Next { get; set; } + public Uri? Next { get; set; } [JsonPropertyName("has_more")] public bool HasMore { get; set; } diff --git a/Streetwriters.Common/Models/ListUsersResponse.cs b/Streetwriters.Common/Models/ListUsersResponse.cs index f0b1c0a..3e71ddb 100644 --- a/Streetwriters.Common/Models/ListUsersResponse.cs +++ b/Streetwriters.Common/Models/ListUsersResponse.cs @@ -9,7 +9,7 @@ namespace Streetwriters.Common.Models public bool Success { get; set; } [JsonPropertyName("response")] - public PaddleUser[] Users { get; set; } + public PaddleUser[]? Users { get; set; } } public class PaddleUser @@ -24,22 +24,22 @@ namespace Streetwriters.Common.Models public long UserId { get; set; } [JsonPropertyName("user_email")] - public string UserEmail { get; set; } + public string? UserEmail { get; set; } [JsonPropertyName("marketing_consent")] public bool MarketingConsent { get; set; } [JsonPropertyName("update_url")] - public string UpdateUrl { get; set; } + public string? UpdateUrl { get; set; } [JsonPropertyName("cancel_url")] - public string CancelUrl { get; set; } + public string? CancelUrl { get; set; } [JsonPropertyName("state")] - public string State { get; set; } + public string? State { get; set; } [JsonPropertyName("signup_date")] - public string SignupDate { get; set; } + public string? SignupDate { get; set; } [JsonPropertyName("quantity")] public long Quantity { get; set; } diff --git a/Streetwriters.Common/Models/MFAConfig.cs b/Streetwriters.Common/Models/MFAConfig.cs index 735945b..8c60530 100644 --- a/Streetwriters.Common/Models/MFAConfig.cs +++ b/Streetwriters.Common/Models/MFAConfig.cs @@ -22,8 +22,8 @@ namespace Streetwriters.Common.Models public class MFAConfig { public bool IsEnabled { get; set; } - public string PrimaryMethod { get; set; } - public string SecondaryMethod { get; set; } + public required string PrimaryMethod { get; set; } + public string? SecondaryMethod { get; set; } public int RemainingValidCodes { get; set; } } } diff --git a/Streetwriters.Common/Models/Offer.cs b/Streetwriters.Common/Models/Offer.cs index 5940d98..2f37cf9 100644 --- a/Streetwriters.Common/Models/Offer.cs +++ b/Streetwriters.Common/Models/Offer.cs @@ -29,25 +29,25 @@ using Streetwriters.Common.Interfaces; namespace Streetwriters.Common.Models { - public class Offer : IOffer + public class Offer { public Offer() { - Id = ObjectId.GenerateNewId().ToString(); + Id = ObjectId.GenerateNewId(); } [BsonId] [BsonRepresentation(BsonType.ObjectId)] [JsonPropertyName("id")] - public string Id { get; set; } + public ObjectId Id { get; set; } [JsonPropertyName("appId")] public ApplicationType AppId { get; set; } [JsonPropertyName("promoCode")] - public string PromoCode { get; set; } + public required string PromoCode { get; set; } [JsonPropertyName("codes")] - public PromoCode[] Codes { get; set; } + public required PromoCode[] Codes { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Common/Models/PaddleResponse.cs b/Streetwriters.Common/Models/PaddleResponse.cs index 5ccb625..68bd396 100644 --- a/Streetwriters.Common/Models/PaddleResponse.cs +++ b/Streetwriters.Common/Models/PaddleResponse.cs @@ -10,7 +10,7 @@ namespace Streetwriters.Common.Models public partial class PaddleResponse { [JsonPropertyName("error")] - public PaddleError Error { get; set; } + public PaddleError? Error { get; set; } } public class PaddleError diff --git a/Streetwriters.Common/Models/PromoCode.cs b/Streetwriters.Common/Models/PromoCode.cs index 28faa06..d0b6736 100644 --- a/Streetwriters.Common/Models/PromoCode.cs +++ b/Streetwriters.Common/Models/PromoCode.cs @@ -35,6 +35,6 @@ namespace Streetwriters.Common.Models public SubscriptionProvider Provider { get; set; } [JsonPropertyName("code")] - public string Code { get; set; } + public required string Code { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Common/Models/RefundPaymentResponse.cs b/Streetwriters.Common/Models/RefundPaymentResponse.cs index d74a294..7d8ba42 100644 --- a/Streetwriters.Common/Models/RefundPaymentResponse.cs +++ b/Streetwriters.Common/Models/RefundPaymentResponse.cs @@ -9,7 +9,7 @@ namespace Streetwriters.Common.Models public bool Success { get; set; } [JsonPropertyName("response")] - public Refund Refund { get; set; } + public required Refund Refund { get; set; } } public partial class Refund diff --git a/Streetwriters.Common/Models/Response.cs b/Streetwriters.Common/Models/Response.cs index b7f4f4f..9c704cf 100644 --- a/Streetwriters.Common/Models/Response.cs +++ b/Streetwriters.Common/Models/Response.cs @@ -30,6 +30,6 @@ namespace Streetwriters.Common.Models public bool Success { get; set; } public int StatusCode { get; set; } [JsonIgnore] - public HttpContent Content { get; set; } + public HttpContent? Content { get; set; } } } diff --git a/Streetwriters.Common/Models/Subscription.cs b/Streetwriters.Common/Models/Subscription.cs index b8a8f88..6272bcf 100644 --- a/Streetwriters.Common/Models/Subscription.cs +++ b/Streetwriters.Common/Models/Subscription.cs @@ -65,8 +65,12 @@ namespace Streetwriters.Common.Models [BsonRepresentation(BsonType.Int32)] [JsonPropertyName("type")] - [Obsolete("Use SubscriptionPlan and SubscriptionStatus instead.")] - public SubscriptionType Type { get; set; } + public SubscriptionType Type + { + get; + [Obsolete("Use SubscriptionPlan and SubscriptionStatus instead.")] + set; + } [JsonPropertyName("cancelURL")] public string? CancelURL { get; set; } diff --git a/Streetwriters.Common/Models/SubscriptionPreviewResponse.cs b/Streetwriters.Common/Models/SubscriptionPreviewResponse.cs index 7d57c99..2724cd2 100644 --- a/Streetwriters.Common/Models/SubscriptionPreviewResponse.cs +++ b/Streetwriters.Common/Models/SubscriptionPreviewResponse.cs @@ -10,40 +10,40 @@ namespace Streetwriters.Common.Models public partial class SubscriptionPreviewResponse : PaddleResponse { [JsonPropertyName("data")] - public SubscriptionPreviewData Data { get; set; } + public SubscriptionPreviewData? Data { get; set; } } public partial class SubscriptionPreviewData { [JsonPropertyName("currency_code")] - public string CurrencyCode { get; set; } + public string? CurrencyCode { get; set; } [JsonPropertyName("billing_cycle")] - public BillingCycle BillingCycle { get; set; } + public BillingCycle? BillingCycle { get; set; } [JsonPropertyName("update_summary")] - public UpdateSummary UpdateSummary { get; set; } + public UpdateSummary? UpdateSummary { get; set; } [JsonPropertyName("immediate_transaction")] - public TransactionV2 ImmediateTransaction { get; set; } + public TransactionV2? ImmediateTransaction { get; set; } [JsonPropertyName("next_transaction")] - public TransactionV2 NextTransaction { get; set; } + public TransactionV2? NextTransaction { get; set; } [JsonPropertyName("recurring_transaction_details")] - public Details RecurringTransactionDetails { get; set; } + public Details? RecurringTransactionDetails { get; set; } } public partial class UpdateSummary { [JsonPropertyName("charge")] - public UpdateSummaryItem Charge { get; set; } + public UpdateSummaryItem? Charge { get; set; } [JsonPropertyName("credit")] - public UpdateSummaryItem Credit { get; set; } + public UpdateSummaryItem? Credit { get; set; } [JsonPropertyName("result")] - public UpdateSummaryItem Result { get; set; } + public UpdateSummaryItem? Result { get; set; } } public partial class UpdateSummaryItem diff --git a/Streetwriters.Common/Models/UserModel.cs b/Streetwriters.Common/Models/UserModel.cs index 20f330a..10aee44 100644 --- a/Streetwriters.Common/Models/UserModel.cs +++ b/Streetwriters.Common/Models/UserModel.cs @@ -24,13 +24,13 @@ namespace Streetwriters.Common.Models public class UserModel { [JsonPropertyName("id")] - public string UserId { get; set; } + public required string UserId { get; set; } [JsonPropertyName("email")] - public string Email { get; set; } + public required string Email { get; set; } [JsonPropertyName("phoneNumber")] - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } [JsonPropertyName("isEmailConfirmed")] public bool IsEmailConfirmed { get; set; } @@ -39,7 +39,7 @@ namespace Streetwriters.Common.Models public bool MarketingConsent { get; set; } [JsonPropertyName("mfa")] - public MFAConfig MFA { get; set; } + public required MFAConfig MFA { get; set; } } } diff --git a/Streetwriters.Common/Servers.cs b/Streetwriters.Common/Servers.cs index 1cdeaba..4c1a37f 100644 --- a/Streetwriters.Common/Servers.cs +++ b/Streetwriters.Common/Servers.cs @@ -30,16 +30,16 @@ namespace Streetwriters.Common { public class Server { - public Server(string originCertPath = null, string originCertKeyPath = null) + public Server(string? originCertPath = null, string? originCertKeyPath = null) { if (!string.IsNullOrEmpty(originCertPath) && !string.IsNullOrEmpty(originCertKeyPath)) this.SSLCertificate = X509Certificate2.CreateFromPemFile(originCertPath, originCertKeyPath); } - public string Id { get; set; } + public string? Id { get; set; } public int Port { get; set; } - public string Hostname { get; set; } - public Uri PublicURL { get; set; } - public X509Certificate2 SSLCertificate { get; } + public required string Hostname { get; set; } + public Uri? PublicURL { get; set; } + public X509Certificate2? SSLCertificate { get; } public bool IsSecure { get => this.SSLCertificate != null; } public override string ToString() @@ -93,14 +93,14 @@ namespace Streetwriters.Common public static Server NotesnookAPI { get; } = new(Constants.NOTESNOOK_CERT_PATH, Constants.NOTESNOOK_CERT_KEY_PATH) { Port = Constants.NOTESNOOK_SERVER_PORT, - Hostname = Constants.NOTESNOOK_SERVER_HOST, + Hostname = Constants.NOTESNOOK_SERVER_HOST ?? "localhost", Id = "notesnook-sync" }; public static Server MessengerServer { get; } = new(Constants.SSE_CERT_PATH, Constants.SSE_CERT_KEY_PATH) { Port = Constants.SSE_SERVER_PORT, - Hostname = Constants.SSE_SERVER_HOST, + Hostname = Constants.SSE_SERVER_HOST ?? "localhost", Id = "sse" }; @@ -108,14 +108,14 @@ namespace Streetwriters.Common { PublicURL = Constants.IDENTITY_SERVER_URL, Port = Constants.IDENTITY_SERVER_PORT, - Hostname = Constants.IDENTITY_SERVER_HOST, + Hostname = Constants.IDENTITY_SERVER_HOST ?? "localhost", Id = "auth" }; public static Server SubscriptionServer { get; } = new(Constants.SUBSCRIPTIONS_CERT_PATH, Constants.SUBSCRIPTIONS_CERT_KEY_PATH) { Port = Constants.SUBSCRIPTIONS_SERVER_PORT, - Hostname = Constants.SUBSCRIPTIONS_SERVER_HOST, + Hostname = Constants.SUBSCRIPTIONS_SERVER_HOST ?? "localhost", Id = "subscription" }; } diff --git a/Streetwriters.Common/Services/EmailSender.cs b/Streetwriters.Common/Services/EmailSender.cs index a3256fb..97131ce 100644 --- a/Streetwriters.Common/Services/EmailSender.cs +++ b/Streetwriters.Common/Services/EmailSender.cs @@ -28,8 +28,8 @@ namespace Streetwriters.Common.Services string email, EmailTemplate template, IClient client, - GnuPGContext gpgContext = null, - Dictionary attachments = null + GnuPGContext? gpgContext = null, + Dictionary? attachments = null ) { if (!mailClient.IsConnected) @@ -78,8 +78,8 @@ namespace Streetwriters.Common.Services EmailTemplate template, IClient client, MailboxAddress sender, - GnuPGContext gpgContext = null, - Dictionary attachments = null + GnuPGContext? gpgContext = null, + Dictionary? attachments = null ) { var builder = new BodyBuilder(); diff --git a/Streetwriters.Common/Services/PaddleBillingService.cs b/Streetwriters.Common/Services/PaddleBillingService.cs index 32cedc1..fddb6ab 100644 --- a/Streetwriters.Common/Services/PaddleBillingService.cs +++ b/Streetwriters.Common/Services/PaddleBillingService.cs @@ -129,7 +129,7 @@ namespace Streetwriters.Common.Services public async Task FindCustomerFromTransactionAsync(string transactionId) { var transaction = await GetTransactionAsync(transactionId); - if (transaction == null) return null; + if (transaction?.Transaction?.CustomerId == null) return null; var url = $"{PADDLE_BASE_URI}/customers/{transaction.Transaction.CustomerId}"; var response = await httpClient.GetFromJsonAsync(url); return response; diff --git a/Streetwriters.Common/Services/PaddleService.cs b/Streetwriters.Common/Services/PaddleService.cs index 1ab9801..0a25938 100644 --- a/Streetwriters.Common/Services/PaddleService.cs +++ b/Streetwriters.Common/Services/PaddleService.cs @@ -18,7 +18,7 @@ namespace Streetwriters.Common.Services HttpClient httpClient = new HttpClient(); - public async Task ListUsersAsync( + public async Task ListUsersAsync( string subscriptionId, int results ) @@ -41,7 +41,7 @@ namespace Streetwriters.Common.Services return await response.Content.ReadFromJsonAsync(); } - public async Task ListPaymentsAsync( + public async Task ListPaymentsAsync( string subscriptionId, long planId ) @@ -66,7 +66,7 @@ namespace Streetwriters.Common.Services return await response.Content.ReadFromJsonAsync(); } - public async Task ListTransactionsAsync( + public async Task ListTransactionsAsync( string subscriptionId ) { @@ -86,7 +86,7 @@ namespace Streetwriters.Common.Services return await response.Content.ReadFromJsonAsync(); } - public async Task FindUserFromOrderAsync(string orderId) + public async Task FindUserFromOrderAsync(string orderId) { var url = $"{PADDLE_BASE_URI}/2.0/order/{orderId}/transactions"; var httpClient = new HttpClient(); @@ -101,7 +101,7 @@ namespace Streetwriters.Common.Services ) ); var transactions = await response.Content.ReadFromJsonAsync(); - if (transactions.Transactions.Length == 0) return null; + if (transactions?.Transactions == null || transactions.Transactions.Length == 0) return null; return transactions.Transactions[0].User; } @@ -123,7 +123,7 @@ namespace Streetwriters.Common.Services ); var refundResponse = await response.Content.ReadFromJsonAsync(); - return refundResponse.Success; + return refundResponse?.Success ?? false; } public async Task CancelSubscriptionAsync(string subscriptionId) diff --git a/Streetwriters.Common/WampServers.cs b/Streetwriters.Common/WampServers.cs index 5891a8b..4fab04e 100644 --- a/Streetwriters.Common/WampServers.cs +++ b/Streetwriters.Common/WampServers.cs @@ -32,14 +32,14 @@ namespace Streetwriters.Common { private readonly ConcurrentDictionary Channels = new(); - public string Endpoint { get; set; } - public string Address { get; set; } + public required string Endpoint { get; set; } + public required string Address { get; set; } public T Topics { get; set; } = new T(); - public string Realm { get; set; } + public required string Realm { get; set; } private async Task GetChannelAsync(string topic) { - if (!Channels.TryGetValue(topic, out IWampRealmProxy channel) || !channel.Monitor.IsConnected) + if (!Channels.TryGetValue(topic, out IWampRealmProxy? channel) || channel == null || !channel.Monitor.IsConnected) { channel = await WampHelper.OpenWampChannelAsync(Address, Realm); Channels.AddOrUpdate(topic, (key) => channel, (key, old) => channel); diff --git a/Streetwriters.Data/DbSettings.cs b/Streetwriters.Data/DbSettings.cs index 88d6f28..556ccbe 100644 --- a/Streetwriters.Data/DbSettings.cs +++ b/Streetwriters.Data/DbSettings.cs @@ -23,7 +23,7 @@ namespace Streetwriters.Data { public class DbSettings : IDbSettings { - public string DatabaseName { get; set; } - public string ConnectionString { get; set; } + public required string DatabaseName { get; set; } + public required string ConnectionString { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Data/Streetwriters.Data.csproj b/Streetwriters.Data/Streetwriters.Data.csproj index ccab4e0..95ab00d 100644 --- a/Streetwriters.Data/Streetwriters.Data.csproj +++ b/Streetwriters.Data/Streetwriters.Data.csproj @@ -2,6 +2,7 @@ net8.0 + enable diff --git a/Streetwriters.Identity/Controllers/AccountController.cs b/Streetwriters.Identity/Controllers/AccountController.cs index 5de1911..491539c 100644 --- a/Streetwriters.Identity/Controllers/AccountController.cs +++ b/Streetwriters.Identity/Controllers/AccountController.cs @@ -17,6 +17,7 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -24,9 +25,6 @@ using System.Security.Claims; using System.Text.Json; using System.Threading.Tasks; using AspNetCore.Identity.Mongo.Model; -using IdentityServer4; -using IdentityServer4.Configuration; -using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -74,7 +72,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(clientId); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.FindByIdAsync(userId); + var user = await UserManager.FindByIdAsync(userId) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, clientId)) return BadRequest($"Unable to find user with ID '{userId}'."); switch (type) @@ -86,16 +84,13 @@ namespace Streetwriters.Identity.Controllers var result = await UserManager.ConfirmEmailAsync(user, code); if (!result.Succeeded) return BadRequest(result.Errors.ToErrors()); - if (await UserManager.IsInRoleAsync(user, client.Id)) + if (await UserManager.IsInRoleAsync(user, client.Id) && client.OnEmailConfirmed != null) { await client.OnEmailConfirmed(userId); } if (!await UserManager.GetTwoFactorEnabledAsync(user)) - { await MFAService.EnableMFAAsync(user, MFAMethods.Email); - user = await UserManager.GetUserAsync(User); - } var redirectUrl = $"{client.EmailConfirmedRedirectURL}?userId={userId}"; return RedirectPermanent(redirectUrl); @@ -122,11 +117,12 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'."); if (string.IsNullOrEmpty(newEmail)) { + ArgumentNullException.ThrowIfNull(user.Email); var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.TokenLink(user.Id.ToString(), code, client.Id, TokenType.CONFRIM_EMAIL); await EmailSender.SendConfirmationEmailAsync(user.Email, callbackUrl, client); @@ -144,7 +140,7 @@ namespace Streetwriters.Identity.Controllers { var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); return Ok(UserAccountService.GetUserAsync(client.Id, user.Id.ToString())); } @@ -156,7 +152,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(form.ClientId); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.FindByEmailAsync(form.Email); + var user = await UserManager.FindByEmailAsync(form.Email) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, form.ClientId)) return Ok(); var code = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword"); @@ -176,7 +172,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'."); var subjectId = User.FindFirstValue("sub"); @@ -187,7 +183,7 @@ namespace Streetwriters.Identity.Controllers ClientId = client.Id, SubjectId = subjectId }); - grants = grants.Where((grant) => grant.Data.Contains(jti)); + grants = jti == null ? [] : grants.Where((grant) => grant.Data.Contains(jti)); if (grants.Any()) { foreach (var grant in grants) @@ -203,7 +199,7 @@ namespace Streetwriters.Identity.Controllers public async Task GetAccessTokenFromCode([FromForm] GetAccessTokenForm form) { if (!Clients.IsValidClient(form.ClientId)) return BadRequest("Invalid clientId."); - var user = await UserManager.FindByIdAsync(form.UserId); + var user = await UserManager.FindByIdAsync(form.UserId) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, form.ClientId)) return BadRequest($"Unable to find user with ID '{form.UserId}'."); @@ -224,7 +220,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'."); @@ -232,6 +228,9 @@ namespace Streetwriters.Identity.Controllers { case "change_email": { + ArgumentNullException.ThrowIfNull(form.NewEmail); + ArgumentNullException.ThrowIfNull(form.Password); + ArgumentNullException.ThrowIfNull(form.VerificationCode); var result = await UserManager.ChangeEmailAsync(user, form.NewEmail, form.VerificationCode); if (result.Succeeded) { @@ -251,6 +250,8 @@ namespace Streetwriters.Identity.Controllers } case "change_password": { + ArgumentNullException.ThrowIfNull(form.OldPassword); + ArgumentNullException.ThrowIfNull(form.NewPassword); var result = await UserManager.ChangePasswordAsync(user, form.OldPassword, form.NewPassword); if (result.Succeeded) { @@ -261,6 +262,7 @@ namespace Streetwriters.Identity.Controllers } case "reset_password": { + ArgumentNullException.ThrowIfNull(form.NewPassword); var result = await UserManager.RemovePasswordAsync(user); if (result.Succeeded) { @@ -295,7 +297,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{user.Id}'."); var jti = User.FindFirstValue("jti"); diff --git a/Streetwriters.Identity/Controllers/MFAController.cs b/Streetwriters.Identity/Controllers/MFAController.cs index f2bc718..92200ce 100644 --- a/Streetwriters.Identity/Controllers/MFAController.cs +++ b/Streetwriters.Identity/Controllers/MFAController.cs @@ -51,7 +51,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); try { @@ -83,7 +83,7 @@ namespace Streetwriters.Identity.Controllers [HttpGet("codes")] public async Task GetRecoveryCodes() { - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await UserManager.GetTwoFactorEnabledAsync(user)) return BadRequest("Please enable 2FA."); return Ok(await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 16)); } @@ -97,7 +97,7 @@ namespace Streetwriters.Identity.Controllers var client = Clients.FindClientById(User.FindFirstValue("client_id")); if (client == null) return BadRequest("Invalid client_id."); - var user = await UserManager.FindByIdAsync(User.FindFirstValue("sub")); + var user = await UserManager.GetUserAsync(User); if (user == null) return Ok(); // We cannot expose that the user doesn't exist. await MFAService.SendOTPAsync(user, client, new MultiFactorSetupForm @@ -111,7 +111,7 @@ namespace Streetwriters.Identity.Controllers [HttpPatch] public async Task EnableAuthenticator([FromForm] MultiFactorEnableForm form) { - var user = await UserManager.GetUserAsync(User); + var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found."); if (!await MFAService.VerifyOTPAsync(user, form.VerificationCode, form.Type)) return BadRequest("Invalid verification code."); diff --git a/Streetwriters.Identity/Controllers/SignupController.cs b/Streetwriters.Identity/Controllers/SignupController.cs index ba3ae9c..ca27091 100644 --- a/Streetwriters.Identity/Controllers/SignupController.cs +++ b/Streetwriters.Identity/Controllers/SignupController.cs @@ -88,6 +88,7 @@ namespace Streetwriters.Identity.Controllers if (result.Errors.Any((e) => e.Code == "DuplicateEmail")) { var user = await UserManager.FindByEmailAsync(form.Email); + if (user == null) return BadRequest(new string[] { "User not found." }); if (!await UserManager.IsInRoleAsync(user, client.Id)) { @@ -114,6 +115,8 @@ namespace Streetwriters.Identity.Controllers if (result.Succeeded) { var user = await UserManager.FindByEmailAsync(form.Email); + if (user == null) return BadRequest(new string[] { "User not found after creation." }); + await UserManager.AddToRoleAsync(user, client.Id); if (Constants.IS_SELF_HOSTED) { @@ -124,7 +127,10 @@ namespace Streetwriters.Identity.Controllers await UserManager.AddClaimAsync(user, new Claim("platform", PlatformFromUserAgent(base.HttpContext.Request.Headers.UserAgent))); var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.TokenLink(user.Id.ToString(), code, client.Id, TokenType.CONFRIM_EMAIL); - await EmailSender.SendConfirmationEmailAsync(user.Email, callbackUrl, client); + if (!string.IsNullOrEmpty(user.Email) && callbackUrl != null) + { + await EmailSender.SendConfirmationEmailAsync(user.Email, callbackUrl, client); + } } return Ok(new { @@ -141,8 +147,9 @@ namespace Streetwriters.Identity.Controllers } } - string PlatformFromUserAgent(string userAgent) + static string PlatformFromUserAgent(string? userAgent) { + if (string.IsNullOrEmpty(userAgent)) return "unknown"; return userAgent.Contains("okhttp/") ? "android" : userAgent.Contains("Darwin/") || userAgent.Contains("CFNetwork/") ? "ios" : "web"; } } diff --git a/Streetwriters.Identity/Extensions/HttpContextExtensions.cs b/Streetwriters.Identity/Extensions/HttpContextExtensions.cs index 8581c83..3c2e90e 100644 --- a/Streetwriters.Identity/Extensions/HttpContextExtensions.cs +++ b/Streetwriters.Identity/Extensions/HttpContextExtensions.cs @@ -33,14 +33,14 @@ namespace Microsoft.AspNetCore.Http /// Http context /// Whether to allow x-forwarded-for header check /// IPAddress - public static IPAddress GetRemoteIPAddress(this HttpContext context, bool allowForwarded = true) + public static IPAddress? GetRemoteIPAddress(this HttpContext context, bool allowForwarded = true) { if (allowForwarded) { // if you are allowing these forward headers, please ensure you are restricting context.Connection.RemoteIpAddress // to cloud flare ips: https://www.cloudflare.com/ips/ - string header = (context.Request.Headers["CF-Connecting-IP"].FirstOrDefault() ?? context.Request.Headers["X-Forwarded-For"].FirstOrDefault()); - if (IPAddress.TryParse(header, out IPAddress ip)) + string? header = context.Request.Headers["CF-Connecting-IP"].FirstOrDefault() ?? context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); + if (IPAddress.TryParse(header, out IPAddress? ip)) { return ip; } @@ -48,12 +48,12 @@ namespace Microsoft.AspNetCore.Http return context.Connection.RemoteIpAddress; } - static UserAgentService userAgentService = new UserAgentService(); + static readonly UserAgentService userAgentService = new(); public static string GetClientInfo(this HttpContext httpContext) { - var clientIp = httpContext.GetRemoteIPAddress().ToString(); + var clientIp = httpContext.GetRemoteIPAddress()?.ToString(); var country = httpContext.Request.Headers["CF-IPCountry"]; - var userAgent = httpContext.Request.Headers["User-Agent"]; + var userAgent = httpContext.Request.Headers.UserAgent; var builder = new StringBuilder(); builder.AppendLine($"Date: {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")}"); diff --git a/Streetwriters.Identity/Extensions/MongoDBTicketStore.cs b/Streetwriters.Identity/Extensions/MongoDBTicketStore.cs index a928a88..8c3d8af 100644 --- a/Streetwriters.Identity/Extensions/MongoDBTicketStore.cs +++ b/Streetwriters.Identity/Extensions/MongoDBTicketStore.cs @@ -55,10 +55,9 @@ namespace Microsoft.AspNetCore.Authentication return Task.FromResult(true); } - Task ITicketStore.RetrieveAsync(string key) + Task ITicketStore.RetrieveAsync(string key) { - AuthenticationTicket ticket; - _cache.TryGetValue(key, out ticket); + _cache.TryGetValue(key, out AuthenticationTicket? ticket); return Task.FromResult(ticket); } diff --git a/Streetwriters.Identity/Extensions/UrlExtensions.cs b/Streetwriters.Identity/Extensions/UrlExtensions.cs index 9a7216b..41f3689 100644 --- a/Streetwriters.Identity/Extensions/UrlExtensions.cs +++ b/Streetwriters.Identity/Extensions/UrlExtensions.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc { public static class UrlHelperExtensions { - public static string TokenLink(this IUrlHelper urlHelper, string userId, string code, string clientId, TokenType type) + public static string? TokenLink(this IUrlHelper urlHelper, string userId, string code, string clientId, TokenType type) { return urlHelper.ActionLink( diff --git a/Streetwriters.Identity/Extensions/UserManagerExtensions.cs b/Streetwriters.Identity/Extensions/UserManagerExtensions.cs index d1845dc..cad4c3e 100644 --- a/Streetwriters.Identity/Extensions/UserManagerExtensions.cs +++ b/Streetwriters.Identity/Extensions/UserManagerExtensions.cs @@ -24,8 +24,10 @@ namespace Microsoft.AspNetCore.Identity { public static class UserManagerExtensions { - public static async Task FindRegisteredUserAsync(this UserManager userManager, string email, string clientId) + public static async Task FindRegisteredUserAsync(this UserManager userManager, string? email, string clientId) { + if (email == null) return null; + var user = await userManager.FindByEmailAsync(email); return user != null && await userManager.IsInRoleAsync(user, clientId) ? user : null; } diff --git a/Streetwriters.Identity/Interfaces/IMFAService.cs b/Streetwriters.Identity/Interfaces/IMFAService.cs index b8edfd4..d5f448e 100644 --- a/Streetwriters.Identity/Interfaces/IMFAService.cs +++ b/Streetwriters.Identity/Interfaces/IMFAService.cs @@ -31,7 +31,7 @@ namespace Streetwriters.Identity.Interfaces Task ResetMFAAsync(User user); Task SetSecondaryMethodAsync(User user, string secondaryMethod); string GetPrimaryMethod(User user); - string GetSecondaryMethod(User user); + string? GetSecondaryMethod(User user); Task GetRemainingValidCodesAsync(User user); bool IsValidMFAMethod(string method); bool IsValidMFAMethod(string method, User user); diff --git a/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs b/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs index 618623a..594f9a3 100644 --- a/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs +++ b/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs @@ -36,7 +36,7 @@ namespace Streetwriters.Identity.MessageHandlers var client = Clients.FindClientByAppId(subscription.AppId); if (client == null || user == null) return; - IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id)); + IdentityUserClaim? statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id)); Claim subscriptionClaim = UserService.SubscriptionPlanToClaim(client.Id, subscription); if (statusClaim?.ClaimValue == subscriptionClaim.Value) return; if (statusClaim != null) diff --git a/Streetwriters.Identity/MessageHandlers/DeleteSubscription.cs b/Streetwriters.Identity/MessageHandlers/DeleteSubscription.cs index e2e31e7..f864405 100644 --- a/Streetwriters.Identity/MessageHandlers/DeleteSubscription.cs +++ b/Streetwriters.Identity/MessageHandlers/DeleteSubscription.cs @@ -34,7 +34,7 @@ namespace Streetwriters.Identity.MessageHandlers var client = Clients.FindClientByAppId(message.AppId); if (client == null || user == null) return; - IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == $"{client.Id}:status"); + IdentityUserClaim? statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == $"{client.Id}:status"); if (statusClaim != null) { await userManager.RemoveClaimAsync(user, statusClaim.ToClaim()); diff --git a/Streetwriters.Identity/Models/AuthenticatorDetails.cs b/Streetwriters.Identity/Models/AuthenticatorDetails.cs index 0b5aa15..2bb6263 100644 --- a/Streetwriters.Identity/Models/AuthenticatorDetails.cs +++ b/Streetwriters.Identity/Models/AuthenticatorDetails.cs @@ -21,12 +21,12 @@ namespace Streetwriters.Identity.Models { public class AuthenticatorDetails { - public string SharedKey + public required string SharedKey { get; set; } - public string AuthenticatorUri + public required string AuthenticatorUri { get; set; } diff --git a/Streetwriters.Identity/Models/ChangeEmailForm.cs b/Streetwriters.Identity/Models/ChangeEmailForm.cs index eeb5278..8a2f12c 100644 --- a/Streetwriters.Identity/Models/ChangeEmailForm.cs +++ b/Streetwriters.Identity/Models/ChangeEmailForm.cs @@ -31,7 +31,7 @@ namespace Streetwriters.Identity.Models [Required] [BindProperty(Name = "email")] [EmailAddress] - public string NewEmail + public required string NewEmail { get; set; } diff --git a/Streetwriters.Identity/Models/GetAccessTokenForm.cs b/Streetwriters.Identity/Models/GetAccessTokenForm.cs index 897d4c1..f052b96 100644 --- a/Streetwriters.Identity/Models/GetAccessTokenForm.cs +++ b/Streetwriters.Identity/Models/GetAccessTokenForm.cs @@ -27,21 +27,21 @@ namespace Streetwriters.Identity.Models { [Required] [BindProperty(Name = "authorization_code")] - public string Code + public required string Code { get; set; } [Required] [BindProperty(Name = "user_id")] - public string UserId + public required string UserId { get; set; } [Required] [BindProperty(Name = "client_id")] - public string ClientId + public required string ClientId { get; set; } diff --git a/Streetwriters.Identity/Models/MFAPasswordRequiredResponse.cs b/Streetwriters.Identity/Models/MFAPasswordRequiredResponse.cs index 85882bd..c1c85d3 100644 --- a/Streetwriters.Identity/Models/MFAPasswordRequiredResponse.cs +++ b/Streetwriters.Identity/Models/MFAPasswordRequiredResponse.cs @@ -24,6 +24,6 @@ namespace Streetwriters.Identity.Models public class MFAPasswordRequiredResponse { [JsonPropertyName("token")] - public string Token { get; set; } + public required string Token { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Identity/Models/MFARequiredResponse.cs b/Streetwriters.Identity/Models/MFARequiredResponse.cs index d812ad4..d51bdde 100644 --- a/Streetwriters.Identity/Models/MFARequiredResponse.cs +++ b/Streetwriters.Identity/Models/MFARequiredResponse.cs @@ -24,12 +24,12 @@ namespace Streetwriters.Identity.Models public class MFARequiredResponse { [JsonPropertyName("primaryMethod")] - public string PrimaryMethod { get; set; } + public required string PrimaryMethod { get; set; } [JsonPropertyName("secondaryMethod")] - public string SecondaryMethod { get; set; } + public string? SecondaryMethod { get; set; } [JsonPropertyName("token")] - public string Token { get; set; } + public string? Token { get; set; } [JsonPropertyName("phoneNumber")] - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Identity/Models/MultiFactorEnableForm.cs b/Streetwriters.Identity/Models/MultiFactorEnableForm.cs index 35a015a..1923cbb 100644 --- a/Streetwriters.Identity/Models/MultiFactorEnableForm.cs +++ b/Streetwriters.Identity/Models/MultiFactorEnableForm.cs @@ -28,14 +28,14 @@ namespace Streetwriters.Identity.Models [DataType(DataType.Text)] [Display(Name = "Authenticator type")] [BindProperty(Name = "type")] - public string Type { get; set; } + public required string Type { get; set; } [Required] [StringLength(6, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Text)] [Display(Name = "Verification Code")] [BindProperty(Name = "code")] - public string VerificationCode { get; set; } + public required string VerificationCode { get; set; } [BindProperty(Name = "isFallback")] public bool IsFallback { get; set; } diff --git a/Streetwriters.Identity/Models/MultiFactorSetupForm.cs b/Streetwriters.Identity/Models/MultiFactorSetupForm.cs index 9709649..817086f 100644 --- a/Streetwriters.Identity/Models/MultiFactorSetupForm.cs +++ b/Streetwriters.Identity/Models/MultiFactorSetupForm.cs @@ -28,10 +28,10 @@ namespace Streetwriters.Identity.Models [Required] [Display(Name = "Authenticator type")] [BindProperty(Name = "type")] - public string Type { get; set; } + public required string Type { get; set; } [Display(Name = "Phone number")] [BindProperty(Name = "phoneNumber")] - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } } } \ No newline at end of file diff --git a/Streetwriters.Identity/Models/ResetPasswordForm.cs b/Streetwriters.Identity/Models/ResetPasswordForm.cs index 2af9746..4f06f9c 100644 --- a/Streetwriters.Identity/Models/ResetPasswordForm.cs +++ b/Streetwriters.Identity/Models/ResetPasswordForm.cs @@ -27,14 +27,14 @@ namespace Streetwriters.Identity.Models { [Required] [BindProperty(Name = "email")] - public string Email + public required string Email { get; set; } [Required] [BindProperty(Name = "client_id")] - public string ClientId + public required string ClientId { get; set; } diff --git a/Streetwriters.Identity/Models/SignupForm.cs b/Streetwriters.Identity/Models/SignupForm.cs index 92b17ca..dbf1033 100644 --- a/Streetwriters.Identity/Models/SignupForm.cs +++ b/Streetwriters.Identity/Models/SignupForm.cs @@ -28,7 +28,7 @@ namespace Streetwriters.Identity.Models [Required] [StringLength(120, ErrorMessage = "Password must be longer than or equal to 8 characters.", MinimumLength = 8)] [BindProperty(Name = "password")] - public string Password + public required string Password { get; set; } @@ -36,20 +36,20 @@ namespace Streetwriters.Identity.Models [Required] [BindProperty(Name = "email")] [EmailAddress] - public string Email + public required string Email { get; set; } [BindProperty(Name = "username")] - public string Username + public string? Username { get; set; } [Required] [BindProperty(Name = "client_id")] - public string ClientId + public required string ClientId { get; set; } diff --git a/Streetwriters.Identity/Models/TwoFactorLoginForm.cs b/Streetwriters.Identity/Models/TwoFactorLoginForm.cs index 0e12d08..87bd03b 100644 --- a/Streetwriters.Identity/Models/TwoFactorLoginForm.cs +++ b/Streetwriters.Identity/Models/TwoFactorLoginForm.cs @@ -30,7 +30,7 @@ namespace Streetwriters.Identity.Models [DataType(DataType.Text)] [Display(Name = "Authenticator code")] [BindProperty(Name = "code")] - public string Code { get; set; } + public required string Code { get; set; } [BindProperty(Name = "rememberMachine")] public bool RememberMachine { get; set; } diff --git a/Streetwriters.Identity/Models/UpdateUserForm.cs b/Streetwriters.Identity/Models/UpdateUserForm.cs index 774c0cb..44b6016 100644 --- a/Streetwriters.Identity/Models/UpdateUserForm.cs +++ b/Streetwriters.Identity/Models/UpdateUserForm.cs @@ -27,7 +27,7 @@ namespace Streetwriters.Identity.Models { [Required] [BindProperty(Name = "type")] - public string Type + public required string Type { get; set; } @@ -39,33 +39,33 @@ namespace Streetwriters.Identity.Models } [BindProperty(Name = "old_password")] - public string OldPassword + public string? OldPassword { get; set; } [BindProperty(Name = "new_password")] - public string NewPassword + public string? NewPassword { get; set; } [BindProperty(Name = "password")] - public string Password + public string? Password { get; set; } [BindProperty(Name = "new_email")] - public string NewEmail + public string? NewEmail { get; set; } [BindProperty(Name = "verification_code")] - public string VerificationCode + public string? VerificationCode { get; set; } diff --git a/Streetwriters.Identity/Program.cs b/Streetwriters.Identity/Program.cs index 236627e..7bcbb03 100644 --- a/Streetwriters.Identity/Program.cs +++ b/Streetwriters.Identity/Program.cs @@ -57,7 +57,7 @@ namespace Streetwriters.Identity { options.Limits.MaxRequestBodySize = long.MaxValue; options.ListenAnyIP(Servers.IdentityServer.Port); - if (Servers.IdentityServer.IsSecure) + if (Servers.IdentityServer.IsSecure && Servers.IdentityServer.SSLCertificate != null) { options.ListenAnyIP(443, listenerOptions => { diff --git a/Streetwriters.Identity/Services/CustomIntrospectionResponseGenerator.cs b/Streetwriters.Identity/Services/CustomIntrospectionResponseGenerator.cs index 0d8b7f6..d8c7a13 100644 --- a/Streetwriters.Identity/Services/CustomIntrospectionResponseGenerator.cs +++ b/Streetwriters.Identity/Services/CustomIntrospectionResponseGenerator.cs @@ -43,9 +43,9 @@ namespace Streetwriters.Identity.Services { var result = await base.ProcessAsync(validationResult); - if (result.TryGetValue("sub", out object userId)) + if (result.TryGetValue("sub", out object? userId) && userId != null) { - var user = await UserManager.FindByIdAsync(userId.ToString()); + var user = await UserManager.FindByIdAsync(userId.ToString() ?? ""); if (user == null || user.Claims == null) return result; var verifiedClaim = user.Claims.Find((c) => c.ClaimType == "verified"); @@ -57,7 +57,7 @@ namespace Streetwriters.Identity.Services user.Claims.ForEach((claim) => { - if (claim.ClaimType == "verified" || claim.ClaimType == "hcli") return; + if (claim.ClaimType == null || claim.ClaimType == "verified" || claim.ClaimType == "hcli") return; result.TryAdd(claim.ClaimType, claim.ClaimValue); }); result.TryAdd("verified", user.EmailConfirmed.ToString().ToLowerInvariant()); diff --git a/Streetwriters.Identity/Services/MFAService.cs b/Streetwriters.Identity/Services/MFAService.cs index de423dc..021f4a0 100644 --- a/Streetwriters.Identity/Services/MFAService.cs +++ b/Streetwriters.Identity/Services/MFAService.cs @@ -101,18 +101,18 @@ namespace Streetwriters.Identity.Services public string GetPrimaryMethod(User user) { - return this.GetClaimValue(user, MFAService.PRIMARY_METHOD_CLAIM, MFAMethods.Email); + return GetClaimValue(user, MFAService.PRIMARY_METHOD_CLAIM) ?? MFAMethods.Email; } - public string GetSecondaryMethod(User user) + public string? GetSecondaryMethod(User user) { - return this.GetClaimValue(user, MFAService.SECONDARY_METHOD_CLAIM); + return GetClaimValue(user, MFAService.SECONDARY_METHOD_CLAIM); } - public string GetClaimValue(User user, string claimType, string defaultValue = null) + public static string? GetClaimValue(User user, string claimType) { var claim = user.Claims.FirstOrDefault((c) => c.ClaimType == claimType); - return claim != null ? claim.ClaimValue : defaultValue; + return claim?.ClaimValue; } public Task GetRemainingValidCodesAsync(User user) @@ -158,6 +158,8 @@ namespace Streetwriters.Identity.Services await UserManager.ResetAuthenticatorKeyAsync(user); unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); } + ArgumentNullException.ThrowIfNull(unformattedKey); + ArgumentNullException.ThrowIfNull(user.Email); return new AuthenticatorDetails { @@ -183,10 +185,12 @@ namespace Streetwriters.Identity.Services switch (method) { case "email": + ArgumentNullException.ThrowIfNull(user.Email); string emailOTP = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); await EmailSender.Send2FACodeEmailAsync(user.Email, emailOTP, client); break; case "sms": + ArgumentNullException.ThrowIfNull(form.PhoneNumber); await UserManager.SetPhoneNumberAsync(user, form.PhoneNumber); var id = await SMSSender.SendOTPAsync(form.PhoneNumber, client); logger.LogInformation("SMS OTP sent for user: {UserId}, SMS ID: {SmsId}", user.Id, id); @@ -200,13 +204,14 @@ namespace Streetwriters.Identity.Services { if (method == MFAMethods.SMS) { - var id = this.GetClaimValue(user, MFAService.SMS_ID_CLAIM); + var id = GetClaimValue(user, MFAService.SMS_ID_CLAIM); if (string.IsNullOrEmpty(id)) throw new Exception("Could not find associated SMS verify id. Please try sending the code again."); if (await SMSSender.VerifyOTPAsync(id, code)) { // Auto confirm user phone number if not confirmed if (!await UserManager.IsPhoneNumberConfirmedAsync(user)) { + ArgumentNullException.ThrowIfNull(user.PhoneNumber); var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, user.PhoneNumber); await UserManager.VerifyChangePhoneNumberTokenAsync(user, token, user.PhoneNumber); } @@ -238,7 +243,7 @@ namespace Streetwriters.Identity.Services return method == MFAMethods.Email || method == MFAMethods.SMS ? TokenOptions.DefaultPhoneProvider : UserManager.Options.Tokens.AuthenticatorTokenProvider; } - private string FormatKey(string unformattedKey) + private static string FormatKey(string unformattedKey) { var result = new StringBuilder(); int currentPosition = 0; @@ -255,7 +260,7 @@ namespace Streetwriters.Identity.Services return result.ToString().ToLowerInvariant(); } - private string GenerateQrCodeUri(string email, string unformattedKey, string issuer) + private static string GenerateQrCodeUri(string email, string unformattedKey, string issuer) { const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; diff --git a/Streetwriters.Identity/Services/ProfileService.cs b/Streetwriters.Identity/Services/ProfileService.cs index 129cba1..47f50fd 100644 --- a/Streetwriters.Identity/Services/ProfileService.cs +++ b/Streetwriters.Identity/Services/ProfileService.cs @@ -43,7 +43,7 @@ namespace Streetwriters.Identity.Services public async Task GetProfileDataAsync(ProfileDataRequestContext context) { - User user = await UserManager.GetUserAsync(context.Subject); + User? user = await UserManager.GetUserAsync(context.Subject); if (user == null) return; IList roles = await UserManager.GetRolesAsync(user); diff --git a/Streetwriters.Identity/Services/TemplatedEmailSender.cs b/Streetwriters.Identity/Services/TemplatedEmailSender.cs index f7f5f96..9001b7b 100644 --- a/Streetwriters.Identity/Services/TemplatedEmailSender.cs +++ b/Streetwriters.Identity/Services/TemplatedEmailSender.cs @@ -60,35 +60,35 @@ namespace Streetwriters.Identity.Services EmailSender = emailSender; } - EmailTemplate Email2FATemplate = new EmailTemplate + readonly EmailTemplate Email2FATemplate = new() { Html = HtmlHelper.ReadMinifiedHtmlFile("Templates/Email2FACode.html"), Text = File.ReadAllText("Templates/Email2FACode.txt"), Subject = "Your {{app_name}} account 2FA code", }; - EmailTemplate ConfirmEmailTemplate = new EmailTemplate + readonly EmailTemplate ConfirmEmailTemplate = new() { Html = HtmlHelper.ReadMinifiedHtmlFile("Templates/ConfirmEmail.html"), Text = File.ReadAllText("Templates/ConfirmEmail.txt"), Subject = "Confirm your {{app_name}} account", }; - EmailTemplate ConfirmChangeEmailTemplate = new EmailTemplate + readonly EmailTemplate ConfirmChangeEmailTemplate = new() { Html = HtmlHelper.ReadMinifiedHtmlFile("Templates/EmailChangeConfirmation.html"), Text = File.ReadAllText("Templates/EmailChangeConfirmation.txt"), Subject = "Change {{app_name}} account email address", }; - EmailTemplate PasswordResetEmailTemplate = new EmailTemplate + readonly EmailTemplate PasswordResetEmailTemplate = new() { Html = HtmlHelper.ReadMinifiedHtmlFile("Templates/ResetAccountPassword.html"), Text = File.ReadAllText("Templates/ResetAccountPassword.txt"), Subject = "Reset {{app_name}} account password", }; - EmailTemplate FailedLoginAlertTemplate = new EmailTemplate + readonly EmailTemplate FailedLoginAlertTemplate = new() { Html = HtmlHelper.ReadMinifiedHtmlFile("Templates/FailedLoginAlert.html"), Text = File.ReadAllText("Templates/FailedLoginAlert.txt"), @@ -97,12 +97,12 @@ namespace Streetwriters.Identity.Services public async Task Send2FACodeEmailAsync(string email, string code, IClient client) { - var template = new EmailTemplate + var template = new EmailTemplate() { Html = Email2FATemplate.Html, Text = Email2FATemplate.Text, Subject = Email2FATemplate.Subject, - Data = new { app_name = client.Name, code = code }, + Data = new { app_name = client.Name, code }, }; await EmailSender.SendEmailAsync(email, template, client, NNGnuPGContext); } @@ -113,7 +113,7 @@ namespace Streetwriters.Identity.Services IClient client ) { - var template = new EmailTemplate + var template = new EmailTemplate() { Html = ConfirmEmailTemplate.Html, Text = ConfirmEmailTemplate.Text, @@ -129,12 +129,12 @@ namespace Streetwriters.Identity.Services IClient client ) { - var template = new EmailTemplate + var template = new EmailTemplate() { Html = ConfirmChangeEmailTemplate.Html, Text = ConfirmChangeEmailTemplate.Text, Subject = ConfirmChangeEmailTemplate.Subject, - Data = new { app_name = client.Name, code = code }, + Data = new { app_name = client.Name, code }, }; await EmailSender.SendEmailAsync(email, template, client, NNGnuPGContext); } @@ -145,7 +145,7 @@ namespace Streetwriters.Identity.Services IClient client ) { - var template = new EmailTemplate + var template = new EmailTemplate() { Html = PasswordResetEmailTemplate.Html, Text = PasswordResetEmailTemplate.Text, @@ -157,7 +157,7 @@ namespace Streetwriters.Identity.Services public async Task SendFailedLoginAlertAsync(string email, string deviceInfo, IClient client) { - var template = new EmailTemplate + var template = new EmailTemplate() { Html = FailedLoginAlertTemplate.Html, Text = FailedLoginAlertTemplate.Text, @@ -176,7 +176,7 @@ namespace Streetwriters.Identity.Services { IConfiguration PgpKeySettings { get; set; } = pgpKeySettings; - protected override string GetPasswordForKey(PgpSecretKey key) + protected override string? GetPasswordForKey(PgpSecretKey key) { return PgpKeySettings[key.KeyId.ToString("X")]; } diff --git a/Streetwriters.Identity/Services/UserAccountService.cs b/Streetwriters.Identity/Services/UserAccountService.cs index 0681889..fcfe56a 100644 --- a/Streetwriters.Identity/Services/UserAccountService.cs +++ b/Streetwriters.Identity/Services/UserAccountService.cs @@ -15,7 +15,7 @@ namespace Streetwriters.Identity.Services public async Task GetUserAsync(string clientId, string userId) { var user = await userManager.FindByIdAsync(userId); - if (!await UserService.IsUserValidAsync(userManager, user, clientId)) + if (user == null || !await UserService.IsUserValidAsync(userManager, user, clientId)) return null; var claims = await userManager.GetClaimsAsync(user); @@ -25,7 +25,9 @@ namespace Streetwriters.Identity.Services { await mfaService.EnableMFAAsync(user, MFAMethods.Email); user = await userManager.FindByIdAsync(userId); + ArgumentNullException.ThrowIfNull(user); } + ArgumentNullException.ThrowIfNull(user.Email); return new UserModel { @@ -46,7 +48,7 @@ namespace Streetwriters.Identity.Services public async Task DeleteUserAsync(string clientId, string userId, string password) { var user = await userManager.FindByIdAsync(userId); - if (!await UserService.IsUserValidAsync(userManager, user, clientId)) return; + if (user == null || !await UserService.IsUserValidAsync(userManager, user, clientId)) return; if (!await userManager.CheckPasswordAsync(user, password)) throw new Exception("Wrong password."); diff --git a/Streetwriters.Identity/Services/UserService.cs b/Streetwriters.Identity/Services/UserService.cs index 0f0f0cd..7882ba6 100644 --- a/Streetwriters.Identity/Services/UserService.cs +++ b/Streetwriters.Identity/Services/UserService.cs @@ -32,7 +32,7 @@ namespace Streetwriters.Identity.Services private static SubscriptionPlan? GetUserSubscriptionPlan(string clientId, User user) { var claimKey = GetClaimKey(clientId); - var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey).ClaimValue; + var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey)?.ClaimValue; switch (status) { case "free": diff --git a/Streetwriters.Identity/Startup.cs b/Streetwriters.Identity/Startup.cs index 6d24f0f..9ab99fb 100644 --- a/Streetwriters.Identity/Startup.cs +++ b/Streetwriters.Identity/Startup.cs @@ -263,7 +263,7 @@ namespace Streetwriters.Identity }); } - private void AddOperationalStore(IServiceCollection services, TokenCleanupOptions tokenCleanUpOptions = null) + private static void AddOperationalStore(IServiceCollection services, TokenCleanupOptions? tokenCleanUpOptions = null) { BsonClassMap.RegisterClassMap(cm => { @@ -279,7 +279,7 @@ namespace Streetwriters.Identity { q.UseMicrosoftDependencyInjectionJobFactory(); - if (tokenCleanUpOptions.Enable) + if (tokenCleanUpOptions?.Enable == true) { var jobKey = new JobKey("TokenCleanupJob"); q.AddJob(opts => opts.WithIdentity(jobKey)); diff --git a/Streetwriters.Identity/Streetwriters.Identity.csproj b/Streetwriters.Identity/Streetwriters.Identity.csproj index 58fedd7..4358bed 100644 --- a/Streetwriters.Identity/Streetwriters.Identity.csproj +++ b/Streetwriters.Identity/Streetwriters.Identity.csproj @@ -3,6 +3,7 @@ net8.0 Streetwriters.Identity.Program + enable diff --git a/Streetwriters.Identity/Validation/BearerTokenValidator.cs b/Streetwriters.Identity/Validation/BearerTokenValidator.cs index d22eaf2..4d51426 100644 --- a/Streetwriters.Identity/Validation/BearerTokenValidator.cs +++ b/Streetwriters.Identity/Validation/BearerTokenValidator.cs @@ -33,7 +33,7 @@ namespace Streetwriters.Identity.Validation /// public static BearerTokenUsageValidationResult ValidateAuthorizationHeader(HttpContext context) { - var authorizationHeader = context.Request.Headers["Authorization"].FirstOrDefault(); + var authorizationHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (!string.IsNullOrEmpty(authorizationHeader)) { var header = authorizationHeader.Trim(); diff --git a/Streetwriters.Identity/Validation/CustomResourceOwnerValidator.cs b/Streetwriters.Identity/Validation/CustomResourceOwnerValidator.cs index 6ae6e1b..aaf8b5e 100644 --- a/Streetwriters.Identity/Validation/CustomResourceOwnerValidator.cs +++ b/Streetwriters.Identity/Validation/CustomResourceOwnerValidator.cs @@ -84,7 +84,7 @@ namespace Streetwriters.Identity.Validation var mfaCode = context.Request.Raw["mfa:code"]; var mfaMethod = context.Request.Raw["mfa:method"]; - if (string.IsNullOrEmpty(mfaCode) || !MFAService.IsValidMFAMethod(mfaMethod, user)) + if (string.IsNullOrEmpty(mfaCode) || string.IsNullOrEmpty(mfaMethod) || !MFAService.IsValidMFAMethod(mfaMethod, user)) { var sendPhoneNumber = primaryMethod == MFAMethods.SMS || secondaryMethod == MFAMethods.SMS; @@ -95,7 +95,7 @@ namespace Streetwriters.Identity.Validation ["error_description"] = "Multifactor authentication required.", ["data"] = JsonSerializer.Serialize(new MFARequiredResponse { - PhoneNumber = sendPhoneNumber ? Regex.Replace(user.PhoneNumber, @"\d(?!\d{0,3}$)", "*") : null, + PhoneNumber = sendPhoneNumber && user.PhoneNumber != null ? Regex.Replace(user.PhoneNumber, @"\d(?!\d{0,3}$)", "*") : null, PrimaryMethod = primaryMethod, SecondaryMethod = secondaryMethod, Token = token, @@ -117,7 +117,6 @@ namespace Streetwriters.Identity.Validation } else { - var provider = mfaMethod == MFAMethods.Email || mfaMethod == MFAMethods.SMS ? TokenOptions.DefaultPhoneProvider : UserManager.Options.Tokens.AuthenticatorTokenProvider; var isMFACodeValid = await MFAService.VerifyOTPAsync(user, mfaCode, mfaMethod); if (!isMFACodeValid) { diff --git a/Streetwriters.Identity/Validation/EmailGrantValidator.cs b/Streetwriters.Identity/Validation/EmailGrantValidator.cs index d5118d7..ef14e3f 100644 --- a/Streetwriters.Identity/Validation/EmailGrantValidator.cs +++ b/Streetwriters.Identity/Validation/EmailGrantValidator.cs @@ -64,20 +64,17 @@ namespace Streetwriters.Identity.Validation { var email = context.Request.Raw["email"]; var clientId = context.Request.ClientId; - var user = await UserManager.FindRegisteredUserAsync(email, clientId); - if (user == null) + var user = await UserManager.FindRegisteredUserAsync(email, clientId) ?? new User { - user = new User - { - Id = MongoDB.Bson.ObjectId.GenerateNewId(), - Email = email, - UserName = email, - NormalizedEmail = email, - NormalizedUserName = email, - EmailConfirmed = false, - SecurityStamp = "" - }; - } + Id = MongoDB.Bson.ObjectId.GenerateNewId(), + Email = email, + UserName = email, + NormalizedEmail = email, + NormalizedUserName = email, + EmailConfirmed = false, + SecurityStamp = "" + }; + var isMultiFactor = await UserManager.GetTwoFactorEnabledAsync(user); var primaryMethod = isMultiFactor ? MFAService.GetPrimaryMethod(user) : MFAMethods.Email; @@ -88,7 +85,7 @@ namespace Streetwriters.Identity.Validation { ["additional_data"] = new MFARequiredResponse { - PhoneNumber = sendPhoneNumber ? Regex.Replace(user.PhoneNumber, @"\d(?!\d{0,3}$)", "*") : null, + PhoneNumber = sendPhoneNumber && user.PhoneNumber != null ? Regex.Replace(user.PhoneNumber, @"\d(?!\d{0,3}$)", "*") : null, PrimaryMethod = primaryMethod, SecondaryMethod = secondaryMethod, } @@ -96,12 +93,5 @@ namespace Streetwriters.Identity.Validation context.Result.IsError = false; context.Result.Subject = await TokenGenerationService.TransformTokenRequestAsync(context.Request, user, GrantType, new string[] { Config.MFA_GRANT_TYPE_SCOPE }); } - - - string Pluralize(int? value, string singular, string plural) - { - if (value == null) return $"0 {plural}"; - return value == 1 ? $"{value} {singular}" : $"{value} {plural}"; - } } } \ No newline at end of file diff --git a/Streetwriters.Identity/Validation/MFAGrantValidator.cs b/Streetwriters.Identity/Validation/MFAGrantValidator.cs index 7ba1580..888b6b9 100644 --- a/Streetwriters.Identity/Validation/MFAGrantValidator.cs +++ b/Streetwriters.Identity/Validation/MFAGrantValidator.cs @@ -67,6 +67,8 @@ namespace Streetwriters.Identity.Validation context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); var httpContext = HttpContextAccessor.HttpContext; + if (httpContext == null) return; + var tokenResult = BearerTokenValidator.ValidateAuthorizationHeader(httpContext); if (!tokenResult.TokenFound) return; diff --git a/Streetwriters.Identity/Validation/MFAPasswordGrantValidator.cs b/Streetwriters.Identity/Validation/MFAPasswordGrantValidator.cs index 9952954..2b12fc6 100644 --- a/Streetwriters.Identity/Validation/MFAPasswordGrantValidator.cs +++ b/Streetwriters.Identity/Validation/MFAPasswordGrantValidator.cs @@ -58,6 +58,8 @@ namespace Streetwriters.Identity.Validation context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); var httpContext = HttpContextAccessor.HttpContext; + if (httpContext == null) return; + var tokenResult = BearerTokenValidator.ValidateAuthorizationHeader(httpContext); if (!tokenResult.TokenFound) return; diff --git a/Streetwriters.Messenger/Helpers/SSEHelper.cs b/Streetwriters.Messenger/Helpers/SSEHelper.cs index 1125aec..7481bfc 100644 --- a/Streetwriters.Messenger/Helpers/SSEHelper.cs +++ b/Streetwriters.Messenger/Helpers/SSEHelper.cs @@ -26,7 +26,7 @@ namespace Streetwriters.Messenger.Helpers { public class SSEHelper { - public static async Task SendEventToUserAsync(string data, IServerSentEventsService sseService, string userId, string originTokenId = null) + public static async Task SendEventToUserAsync(string data, IServerSentEventsService sseService, string userId, string? originTokenId = null) { var clients = sseService.GetClients().Where(c => c.User.FindFirstValue("sub") == userId); foreach (var client in clients) diff --git a/Streetwriters.Messenger/Program.cs b/Streetwriters.Messenger/Program.cs index 546bad2..6768830 100644 --- a/Streetwriters.Messenger/Program.cs +++ b/Streetwriters.Messenger/Program.cs @@ -51,7 +51,7 @@ namespace Streetwriters.Messenger { options.Limits.MaxRequestBodySize = long.MaxValue; options.ListenAnyIP(Servers.MessengerServer.Port); - if (Servers.MessengerServer.IsSecure) + if (Servers.MessengerServer.IsSecure && Servers.MessengerServer.SSLCertificate != null) { options.ListenAnyIP(443, listenerOptions => { diff --git a/Streetwriters.Messenger/Streetwriters.Messenger.csproj b/Streetwriters.Messenger/Streetwriters.Messenger.csproj index b31cbf4..d208777 100644 --- a/Streetwriters.Messenger/Streetwriters.Messenger.csproj +++ b/Streetwriters.Messenger/Streetwriters.Messenger.csproj @@ -3,6 +3,7 @@ net8.0 Streetwriters.Messenger.Program + enable