mirror of
https://github.com/streetwriters/notesnook-sync-server.git
synced 2026-06-03 03:38:06 +02:00
global: add null safety checks
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Notesnook.API.Controllers
|
||||
[Authorize(Policy = "Notesnook")]
|
||||
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> CreateInboxItemAsync([FromBody] InboxSyncItem request)
|
||||
{
|
||||
var userId = User.FindFirstValue("sub");
|
||||
var userId = User.GetUserId();
|
||||
try
|
||||
{
|
||||
if (request.Key.Algorithm != Algorithms.XSAL_X25519_7)
|
||||
|
||||
@@ -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<IActionResult> GetUserMonographsAsync()
|
||||
{
|
||||
var userId = this.User.FindFirstValue("sub");
|
||||
if (userId == null) return Unauthorized();
|
||||
var userId = this.User.GetUserId();
|
||||
|
||||
var userMonographs = (await monographs.Collection.FindAsync(
|
||||
Builders<Monograph>.Filter.And(
|
||||
@@ -257,8 +254,7 @@ namespace Notesnook.API.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> 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<string> CleanupContentAsync(ClaimsPrincipal user, string content)
|
||||
private async Task<string> CleanupContentAsync(ClaimsPrincipal user, string? content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content)) return string.Empty;
|
||||
if (Constants.IS_SELF_HOSTED) return content;
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Deserialize<MonographContent>(content);
|
||||
var json = JsonSerializer.Deserialize<MonographContent>(content) ?? throw new Exception("Invalid monograph content.");
|
||||
var html = json.Data;
|
||||
|
||||
if (user.IsUserSubscribed())
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Notesnook.API.Controllers
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> Delete([FromForm] DeleteAccountForm form)
|
||||
{
|
||||
var userId = this.User.FindFirstValue("sub");
|
||||
var userId = this.User.GetUserId();
|
||||
var jti = User.FindFirstValue("jti");
|
||||
try
|
||||
{
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ namespace Notesnook.API.Models;
|
||||
|
||||
public class CompleteMultipartUploadRequestWrapper
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public List<PartETagWrapper> PartETags { get; set; }
|
||||
public string UploadId { get; set; }
|
||||
public required string Key { get; set; }
|
||||
public required List<PartETagWrapper> PartETags { get; set; }
|
||||
public required string UploadId { get; set; }
|
||||
|
||||
public CompleteMultipartUploadRequest ToRequest()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Notesnook.API.Models
|
||||
public class DeleteAccountForm
|
||||
{
|
||||
[Required]
|
||||
public string Password
|
||||
public required string Password
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -17,11 +17,13 @@ You should have received a copy of the Affero GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<string>();
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
public class PartETagWrapper
|
||||
{
|
||||
public int PartNumber { get; set; }
|
||||
public string ETag { get; set; }
|
||||
public string ETag { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<SyncItem>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Notesnook.API.Services
|
||||
public async Task CreateUserAsync()
|
||||
{
|
||||
SignupResponse response = await httpClient.ForwardAsync<SignupResponse>(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
|
||||
|
||||
@@ -119,8 +119,8 @@ namespace Notesnook.API
|
||||
policy.RequireAuthenticatedUser();
|
||||
});
|
||||
|
||||
options.DefaultPolicy = options.GetPolicy("Notesnook");
|
||||
}).AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationResultTransformer>(); ;
|
||||
options.DefaultPolicy = options.GetPolicy("Notesnook") ?? throw new Exception("Notesnook policy not found");
|
||||
}).AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationResultTransformer>();
|
||||
|
||||
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<SyncHub>("/hubs/sync", options =>
|
||||
{
|
||||
options.CloseOnAuthenticationExpiration = false;
|
||||
options.Transports = HttpTransportType.WebSockets;
|
||||
});
|
||||
endpoints.MapHub<SyncV2Hub>("/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<SyncItem>(provider.GetService<MongoDB.Driver.IMongoClient>(), database, collectionName));
|
||||
services.AddKeyedSingleton(collectionName, (provider, key) => MongoDbContext.GetMongoCollection<SyncItem>(provider.GetRequiredService<MongoDB.Driver.IMongoClient>(), database, collectionName));
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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[] { };
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Streetwriters.Common.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts simple interface into an object (assumes that there is only one class of TInterface)
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">Interface type</typeparam>
|
||||
/// <typeparam name="TClass">Class type</typeparam>
|
||||
public class InterfaceConverter<TInterface, TClass> : JsonConverter<TInterface> where TClass : TInterface
|
||||
{
|
||||
public override TInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return JsonSerializer.Deserialize<TClass>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Streetwriters.Common.Extensions
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "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<T>(this IApplicationBuilder app)
|
||||
public static T GetService<T>(this IApplicationBuilder app) where T : notnull
|
||||
{
|
||||
return app.ApplicationServices.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public static T GetScopedService<T>(this IApplicationBuilder app)
|
||||
public static T GetScopedService<T>(this IApplicationBuilder app) where T : notnull
|
||||
{
|
||||
using (var scope = app.ApplicationServices.CreateScope())
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the Affero GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<T> SendRequestAsync<T>(this HttpClient httpClient, string url, IHeaderDictionary headers, HttpMethod method, HttpContent content = null) where T : IResponse, new()
|
||||
public static async Task<T> SendRequestAsync<T>(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<T>();
|
||||
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<T> ForwardAsync<T>(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<T>(url, httpContext.Request.Headers, method, content);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Streetwriters.Common.Extensions
|
||||
{
|
||||
public static IServiceCollection AddRepository<T>(this IServiceCollection services, string collectionName, string database) where T : class
|
||||
{
|
||||
services.AddSingleton((provider) => MongoDbContext.GetMongoCollection<T>(provider.GetService<MongoDB.Driver.IMongoClient>(), database, collectionName));
|
||||
services.AddSingleton((provider) => MongoDbContext.GetMongoCollection<T>(provider.GetRequiredService<MongoDB.Driver.IMongoClient>(), database, collectionName));
|
||||
services.AddScoped<Repository<T>>();
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,6 @@ namespace Streetwriters.Common.Interfaces
|
||||
string SenderName { get; set; }
|
||||
string EmailConfirmedRedirectURL { get; }
|
||||
string AccountRecoveryRedirectURL { get; }
|
||||
Func<string, Task> OnEmailConfirmed { get; set; }
|
||||
Func<string, Task>? OnEmailConfirmed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@ You should have received a copy of the Affero GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace Streetwriters.Common.Interfaces
|
||||
{
|
||||
public interface IDocument
|
||||
{
|
||||
string Id
|
||||
ObjectId Id
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Streetwriters.Common.Interfaces
|
||||
string email,
|
||||
EmailTemplate template,
|
||||
IClient client,
|
||||
GnuPGContext gpgContext = null,
|
||||
Dictionary<string, byte[]> attachments = null
|
||||
GnuPGContext? gpgContext = null,
|
||||
Dictionary<string, byte[]>? attachments = 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,6 @@ namespace Streetwriters.Common.Interfaces
|
||||
{
|
||||
bool Success { get; set; }
|
||||
int StatusCode { get; set; }
|
||||
HttpContent Content { get; set; }
|
||||
HttpContent? Content { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Streetwriters.Common.Interfaces
|
||||
public interface IUserSubscriptionService
|
||||
{
|
||||
[WampProcedure("co.streetwriters.subscriptions.subscriptions.get_user_subscription")]
|
||||
Task<Subscription> GetUserSubscriptionAsync(string clientId, string userId);
|
||||
Task<Subscription?> GetUserSubscriptionAsync(string clientId, string userId);
|
||||
Subscription TransformUserSubscription(Subscription subscription);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -27,6 +27,6 @@ namespace Streetwriters.Common.Messages
|
||||
public class DeleteUserMessage
|
||||
{
|
||||
[JsonPropertyName("userId")]
|
||||
public string UserId { get; set; }
|
||||
public required string UserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<string, Task> OnEmailConfirmed { get; set; }
|
||||
public Func<string, Task>? OnEmailConfirmed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<string, string> CustomData { get; set; }
|
||||
public Dictionary<string, string>? 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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace Streetwriters.Common.Services
|
||||
string email,
|
||||
EmailTemplate template,
|
||||
IClient client,
|
||||
GnuPGContext gpgContext = null,
|
||||
Dictionary<string, byte[]> attachments = null
|
||||
GnuPGContext? gpgContext = null,
|
||||
Dictionary<string, byte[]>? attachments = null
|
||||
)
|
||||
{
|
||||
if (!mailClient.IsConnected)
|
||||
@@ -78,8 +78,8 @@ namespace Streetwriters.Common.Services
|
||||
EmailTemplate template,
|
||||
IClient client,
|
||||
MailboxAddress sender,
|
||||
GnuPGContext gpgContext = null,
|
||||
Dictionary<string, byte[]> attachments = null
|
||||
GnuPGContext? gpgContext = null,
|
||||
Dictionary<string, byte[]>? attachments = null
|
||||
)
|
||||
{
|
||||
var builder = new BodyBuilder();
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Streetwriters.Common.Services
|
||||
public async Task<GetCustomerResponse?> 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<GetCustomerResponse>(url);
|
||||
return response;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Streetwriters.Common.Services
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
public async Task<ListUsersResponse> ListUsersAsync(
|
||||
public async Task<ListUsersResponse?> ListUsersAsync(
|
||||
string subscriptionId,
|
||||
int results
|
||||
)
|
||||
@@ -41,7 +41,7 @@ namespace Streetwriters.Common.Services
|
||||
return await response.Content.ReadFromJsonAsync<ListUsersResponse>();
|
||||
}
|
||||
|
||||
public async Task<ListPaymentsResponse> ListPaymentsAsync(
|
||||
public async Task<ListPaymentsResponse?> ListPaymentsAsync(
|
||||
string subscriptionId,
|
||||
long planId
|
||||
)
|
||||
@@ -66,7 +66,7 @@ namespace Streetwriters.Common.Services
|
||||
return await response.Content.ReadFromJsonAsync<ListPaymentsResponse>();
|
||||
}
|
||||
|
||||
public async Task<ListTransactionsResponse> ListTransactionsAsync(
|
||||
public async Task<ListTransactionsResponse?> ListTransactionsAsync(
|
||||
string subscriptionId
|
||||
)
|
||||
{
|
||||
@@ -86,7 +86,7 @@ namespace Streetwriters.Common.Services
|
||||
return await response.Content.ReadFromJsonAsync<ListTransactionsResponse>();
|
||||
}
|
||||
|
||||
public async Task<PaddleTransactionUser> FindUserFromOrderAsync(string orderId)
|
||||
public async Task<PaddleTransactionUser?> 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<ListTransactionsResponse>();
|
||||
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<RefundPaymentResponse>();
|
||||
return refundResponse.Success;
|
||||
return refundResponse?.Success ?? false;
|
||||
}
|
||||
|
||||
public async Task<bool> CancelSubscriptionAsync(string subscriptionId)
|
||||
|
||||
@@ -32,14 +32,14 @@ namespace Streetwriters.Common
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, IWampRealmProxy> 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<IWampRealmProxy> 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);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the Affero GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<IActionResult> 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");
|
||||
|
||||
@@ -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<IActionResult> 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<IActionResult> 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.");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ namespace Microsoft.AspNetCore.Http
|
||||
/// <param name="context">Http context</param>
|
||||
/// <param name="allowForwarded">Whether to allow x-forwarded-for header check</param>
|
||||
/// <returns>IPAddress</returns>
|
||||
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")}");
|
||||
|
||||
@@ -55,10 +55,9 @@ namespace Microsoft.AspNetCore.Authentication
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
Task<AuthenticationTicket> ITicketStore.RetrieveAsync(string key)
|
||||
Task<AuthenticationTicket?> ITicketStore.RetrieveAsync(string key)
|
||||
{
|
||||
AuthenticationTicket ticket;
|
||||
_cache.TryGetValue(key, out ticket);
|
||||
_cache.TryGetValue(key, out AuthenticationTicket? ticket);
|
||||
return Task.FromResult(ticket);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -24,8 +24,10 @@ namespace Microsoft.AspNetCore.Identity
|
||||
{
|
||||
public static class UserManagerExtensions
|
||||
{
|
||||
public static async Task<User> FindRegisteredUserAsync(this UserManager<User> userManager, string email, string clientId)
|
||||
public static async Task<User?> FindRegisteredUserAsync(this UserManager<User> 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;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Streetwriters.Identity.Interfaces
|
||||
Task<bool> ResetMFAAsync(User user);
|
||||
Task SetSecondaryMethodAsync(User user, string secondaryMethod);
|
||||
string GetPrimaryMethod(User user);
|
||||
string GetSecondaryMethod(User user);
|
||||
string? GetSecondaryMethod(User user);
|
||||
Task<int> GetRemainingValidCodesAsync(User user);
|
||||
bool IsValidMFAMethod(string method);
|
||||
bool IsValidMFAMethod(string method, User user);
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Streetwriters.Identity.MessageHandlers
|
||||
var client = Clients.FindClientByAppId(subscription.AppId);
|
||||
if (client == null || user == null) return;
|
||||
|
||||
IdentityUserClaim<string> statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id));
|
||||
IdentityUserClaim<string>? 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)
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Streetwriters.Identity.MessageHandlers
|
||||
var client = Clients.FindClientByAppId(message.AppId);
|
||||
if (client == null || user == null) return;
|
||||
|
||||
IdentityUserClaim<string> statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == $"{client.Id}:status");
|
||||
IdentityUserClaim<string>? statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == $"{client.Id}:status");
|
||||
if (statusClaim != null)
|
||||
{
|
||||
await userManager.RemoveClaimAsync(user, statusClaim.ToClaim());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Streetwriters.Identity.Models
|
||||
[Required]
|
||||
[BindProperty(Name = "email")]
|
||||
[EmailAddress]
|
||||
public string NewEmail
|
||||
public required string NewEmail
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ namespace Streetwriters.Identity.Models
|
||||
public class MFAPasswordRequiredResponse
|
||||
{
|
||||
[JsonPropertyName("token")]
|
||||
public string Token { get; set; }
|
||||
public required string Token { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<int> 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";
|
||||
|
||||
|
||||
@@ -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<string> roles = await UserManager.GetRolesAsync(user);
|
||||
|
||||
@@ -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")];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Streetwriters.Identity.Services
|
||||
public async Task<UserModel?> 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.");
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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<PersistedGrant>(cm =>
|
||||
{
|
||||
@@ -279,7 +279,7 @@ namespace Streetwriters.Identity
|
||||
{
|
||||
q.UseMicrosoftDependencyInjectionJobFactory();
|
||||
|
||||
if (tokenCleanUpOptions.Enable)
|
||||
if (tokenCleanUpOptions?.Enable == true)
|
||||
{
|
||||
var jobKey = new JobKey("TokenCleanupJob");
|
||||
q.AddJob<TokenCleanupJob>(opts => opts.WithIdentity(jobKey));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user