♻️ Rebuilt own auth infra

This commit is contained in:
LittleSheep 2025-05-16 23:38:33 +08:00
parent aabe8269f5
commit 88977ccda3
10 changed files with 309 additions and 126 deletions

View File

@ -21,24 +21,28 @@ public class ActionLogService(AppDatabase db, GeoIpService geo) : IDisposable
_creationQueue.Enqueue(log); _creationQueue.Enqueue(log);
} }
public void CreateActionLogFromRequest(string action, Dictionary<string, object> meta, HttpRequest request) public void CreateActionLogFromRequest(string action, Dictionary<string, object> meta, HttpRequest request,
Account? account = null)
{ {
if (request.HttpContext.Items["CurrentUser"] is not Account currentUser)
throw new ArgumentException("No user context was found");
if (request.HttpContext.Items["CurrentSession"] is not Auth.Session currentSession)
throw new ArgumentException("No session context was found");
var log = new ActionLog var log = new ActionLog
{ {
Action = action, Action = action,
AccountId = currentUser.Id,
SessionId = currentSession.Id,
Meta = meta, Meta = meta,
UserAgent = request.Headers.UserAgent, UserAgent = request.Headers.UserAgent,
IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(), IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(),
Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString())
}; };
if (request.HttpContext.Items["CurrentUser"] is Account currentUser)
log.AccountId = currentUser.Id;
else if (account != null)
log.AccountId = account.Id;
else
throw new ArgumentException("No user context was found");
if (request.HttpContext.Items["CurrentSession"] is Auth.Session currentSession)
log.SessionId = currentSession.Id;
_creationQueue.Enqueue(log); _creationQueue.Enqueue(log);
} }

View File

@ -17,11 +17,14 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var userId = currentUser.Id; var userId = currentUser.Id;
var totalCount = await db.AccountRelationships var query = db.AccountRelationships.AsQueryable()
.CountAsync(r => r.Account.Id == userId); .Where(r => r.RelatedId == userId);
var relationships = await db.AccountRelationships var totalCount = await query.CountAsync();
.Where(r => r.Account.Id == userId) var relationships = await query
.Include(r => r.Related) .Include(r => r.Related)
.Include(r => r.Related.Profile)
.Include(r => r.Account)
.Include(r => r.Account.Profile)
.Skip(offset) .Skip(offset)
.Take(take) .Take(take)
.ToListAsync(); .ToListAsync();
@ -31,20 +34,36 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
return relationships; return relationships;
} }
public class RelationshipCreateRequest [HttpGet("requests")]
{
[Required] public long UserId { get; set; }
[Required] public RelationshipStatus Status { get; set; }
}
[HttpPost]
[Authorize] [Authorize]
public async Task<ActionResult<Relationship>> CreateRelationship([FromBody] RelationshipCreateRequest request) public async Task<ActionResult<List<Relationship>>> ListSentRequests()
{ {
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(request.UserId); var relationships = await db.AccountRelationships
if (relatedUser is null) return BadRequest("Invalid related user"); .Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending)
.Include(r => r.Related)
.Include(r => r.Related.Profile)
.Include(r => r.Account)
.Include(r => r.Account.Profile)
.ToListAsync();
return relationships;
}
public class RelationshipRequest
{
[Required] public RelationshipStatus Status { get; set; }
}
[HttpPost("{userId:guid}")]
[Authorize]
public async Task<ActionResult<Relationship>> CreateRelationship(Guid userId, [FromBody] RelationshipRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
try try
{ {
@ -58,4 +77,105 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
return BadRequest(err.Message); return BadRequest(err.Message);
} }
} }
[HttpPatch("{userId:guid}")]
[Authorize]
public async Task<ActionResult<Relationship>> UpdateRelationship(Guid userId, [FromBody] RelationshipRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
try
{
var relationship = await rels.UpdateRelationship(currentUser.Id, userId, request.Status);
return relationship;
}
catch (ArgumentException err)
{
return NotFound(err.Message);
}
catch (InvalidOperationException err)
{
return BadRequest(err.Message);
}
}
[HttpPost("{userId:guid}/friends")]
[Authorize]
public async Task<ActionResult<Relationship>> SendFriendRequest(Guid userId)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
try
{
var relationship = await rels.SendFriendRequest(currentUser, relatedUser);
return relationship;
}
catch (InvalidOperationException err)
{
return BadRequest(err.Message);
}
}
[HttpPost("{userId:guid}/friends/accept")]
[Authorize]
public async Task<ActionResult<Relationship>> AcceptFriendRequest(Guid userId)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found.");
try
{
relationship = await rels.AcceptFriendRelationship(relationship);
return relationship;
}
catch (InvalidOperationException err)
{
return BadRequest(err.Message);
}
}
[HttpPost("{userId:guid}/friends/decline")]
[Authorize]
public async Task<ActionResult<Relationship>> DeclineFriendRequest(Guid userId)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found.");
try
{
relationship = await rels.AcceptFriendRelationship(relationship, status: RelationshipStatus.Blocked);
return relationship;
}
catch (InvalidOperationException err)
{
return BadRequest(err.Message);
}
}
[HttpPost("{userId:guid}/block")]
[Authorize]
public async Task<ActionResult<Relationship>> BlockUser(Guid userId)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
try
{
var relationship = await rels.BlockAccount(currentUser, relatedUser);
return relationship;
}
catch (InvalidOperationException err)
{
return BadRequest(err.Message);
}
}
} }

View File

@ -1,32 +1,31 @@
using DysonNetwork.Sphere.Permission;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using NodaTime; using NodaTime;
namespace DysonNetwork.Sphere.Account; namespace DysonNetwork.Sphere.Account;
public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCache cache) public class RelationshipService(AppDatabase db, IMemoryCache cache)
{ {
public async Task<bool> HasExistingRelationship(Account userA, Account userB) public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId)
{ {
var count = await db.AccountRelationships var count = await db.AccountRelationships
.Where(r => (r.AccountId == userA.Id && r.AccountId == userB.Id) || .Where(r => (r.AccountId == accountId && r.RelatedId == relatedId) ||
(r.AccountId == userB.Id && r.AccountId == userA.Id)) (r.AccountId == relatedId && r.AccountId == accountId))
.CountAsync(); .CountAsync();
return count > 0; return count > 0;
} }
public async Task<Relationship?> GetRelationship( public async Task<Relationship?> GetRelationship(
Account account, Guid accountId,
Account related, Guid relatedId,
RelationshipStatus? status, RelationshipStatus? status = null,
bool ignoreExpired = false bool ignoreExpired = false
) )
{ {
var now = Instant.FromDateTimeUtc(DateTime.UtcNow); var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
var queries = db.AccountRelationships var queries = db.AccountRelationships.AsQueryable()
.Where(r => r.AccountId == account.Id && r.AccountId == related.Id); .Where(r => r.AccountId == accountId && r.RelatedId == relatedId);
if (ignoreExpired) queries = queries.Where(r => r.ExpiredAt > now); if (!ignoreExpired) queries = queries.Where(r => r.ExpiredAt > now);
if (status is not null) queries = queries.Where(r => r.Status == status); if (status is not null) queries = queries.Where(r => r.Status == status);
var relationship = await queries.FirstOrDefaultAsync(); var relationship = await queries.FirstOrDefaultAsync();
return relationship; return relationship;
@ -37,7 +36,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
if (status == RelationshipStatus.Pending) if (status == RelationshipStatus.Pending)
throw new InvalidOperationException( throw new InvalidOperationException(
"Cannot create relationship with pending status, use SendFriendRequest instead."); "Cannot create relationship with pending status, use SendFriendRequest instead.");
if (await HasExistingRelationship(sender, target)) if (await HasExistingRelationship(sender.Id, target.Id))
throw new InvalidOperationException("Found existing relationship between you and target user."); throw new InvalidOperationException("Found existing relationship between you and target user.");
var relationship = new Relationship var relationship = new Relationship
@ -49,7 +48,6 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
db.AccountRelationships.Add(relationship); db.AccountRelationships.Add(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await ApplyRelationshipPermissions(relationship);
cache.Remove($"UserFriends_{relationship.AccountId}"); cache.Remove($"UserFriends_{relationship.AccountId}");
cache.Remove($"UserFriends_{relationship.RelatedId}"); cache.Remove($"UserFriends_{relationship.RelatedId}");
@ -57,9 +55,16 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
return relationship; return relationship;
} }
public async Task<Relationship> BlockAccount(Account sender, Account target)
{
if (await HasExistingRelationship(sender.Id, target.Id))
return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
return await CreateRelationship(sender, target, RelationshipStatus.Blocked);
}
public async Task<Relationship> SendFriendRequest(Account sender, Account target) public async Task<Relationship> SendFriendRequest(Account sender, Account target)
{ {
if (await HasExistingRelationship(sender, target)) if (await HasExistingRelationship(sender.Id, target.Id))
throw new InvalidOperationException("Found existing relationship between you and target user."); throw new InvalidOperationException("Found existing relationship between you and target user.");
var relationship = new Relationship var relationship = new Relationship
@ -81,7 +86,9 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
RelationshipStatus status = RelationshipStatus.Friends RelationshipStatus status = RelationshipStatus.Friends
) )
{ {
if (relationship.Status == RelationshipStatus.Pending) if (relationship.Status != RelationshipStatus.Pending)
throw new ArgumentException("Cannot accept friend request that not in pending status.");
if (status == RelationshipStatus.Pending)
throw new ArgumentException("Cannot accept friend request by setting the new status to pending."); throw new ArgumentException("Cannot accept friend request by setting the new status to pending.");
// Whatever the receiver decides to apply which status to the relationship, // Whatever the receiver decides to apply which status to the relationship,
@ -100,27 +107,22 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await Task.WhenAll(
ApplyRelationshipPermissions(relationship),
ApplyRelationshipPermissions(relationshipBackward)
);
cache.Remove($"UserFriends_{relationship.AccountId}"); cache.Remove($"UserFriends_{relationship.AccountId}");
cache.Remove($"UserFriends_{relationship.RelatedId}"); cache.Remove($"UserFriends_{relationship.RelatedId}");
return relationshipBackward; return relationshipBackward;
} }
public async Task<Relationship> UpdateRelationship(Account account, Account related, RelationshipStatus status) public async Task<Relationship> UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status)
{ {
var relationship = await GetRelationship(account, related, status); var relationship = await GetRelationship(accountId, relatedId, status);
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
if (relationship.Status == status) return relationship; if (relationship.Status == status) return relationship;
relationship.Status = status; relationship.Status = status;
db.Update(relationship); db.Update(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await ApplyRelationshipPermissions(relationship); cache.Remove($"UserFriends_{accountId}");
cache.Remove($"UserFriends_{related.Id}"); cache.Remove($"UserFriends_{relatedId}");
return relationship; return relationship;
} }
@ -139,27 +141,10 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
return friends ?? []; return friends ?? [];
} }
private async Task ApplyRelationshipPermissions(Relationship relationship) public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
RelationshipStatus status = RelationshipStatus.Friends)
{ {
// Apply the relationship permissions to casbin enforcer var relationship = await GetRelationship(accountId, relatedId, status);
// domain: the user return relationship is not null;
// status is friends: all permissions are allowed by default, expect specially specified
// status is blocked: all permissions are disallowed by default, expect specially specified
// others: use the default permissions by design
var domain = $"user:{relationship.AccountId.ToString()}";
var target = $"user:{relationship.RelatedId.ToString()}";
await pm.RemovePermissionNode(target, domain, "*");
bool? value = relationship.Status switch
{
RelationshipStatus.Friends => true,
RelationshipStatus.Blocked => false,
_ => null,
};
if (value is null) return;
await pm.AddPermissionNode(target, domain, "*", value);
} }
} }

View File

@ -81,15 +81,15 @@ public class AppDatabase(
{ {
var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App")); var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
dataSourceBuilder.EnableDynamicJson(); dataSourceBuilder.EnableDynamicJson();
dataSourceBuilder.UseNetTopologySuite();
dataSourceBuilder.UseNodaTime(); dataSourceBuilder.UseNodaTime();
var dataSource = dataSourceBuilder.Build();
optionsBuilder.UseNpgsql( optionsBuilder.UseNpgsql(
dataSource, dataSourceBuilder.Build(),
opt => opt opt => opt
.UseNodaTime()
.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
.UseNetTopologySuite() .UseNetTopologySuite()
.UseNodaTime()
).UseSnakeCaseNamingConvention(); ).UseSnakeCaseNamingConvention();
optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) => optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>

View File

@ -0,0 +1,77 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace DysonNetwork.Sphere.Auth;
public static class AuthConstants
{
public const string SchemeName = "DysonToken";
public const string TokenQueryParamName = "tk";
}
public class DysonTokenAuthOptions : AuthenticationSchemeOptions;
public class DysonTokenAuthHandler : AuthenticationHandler<DysonTokenAuthOptions>
{
private TokenValidationParameters _tokenValidationParameters;
public DysonTokenAuthHandler(
IOptionsMonitor<DysonTokenAuthOptions> options,
IConfiguration configuration,
ILoggerFactory logger,
UrlEncoder encoder)
: base(options, logger, encoder)
{
var publicKey = File.ReadAllText(configuration["Jwt:PublicKeyPath"]!);
var rsa = RSA.Create();
rsa.ImportFromPem(publicKey);
_tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "solar-network",
IssuerSigningKey = new RsaSecurityKey(rsa)
};
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = _ExtractToken(Request);
if (string.IsNullOrEmpty(token))
return AuthenticateResult.Fail("No token was provided.");
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, _tokenValidationParameters, out var validatedToken);
var ticket = new AuthenticationTicket(principal, AuthConstants.SchemeName);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
return AuthenticateResult.Fail($"Authentication failed: {ex.Message}");
}
}
private static string? _ExtractToken(HttpRequest request)
{
if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken))
return queryToken;
var authHeader = request.Headers.Authorization.ToString();
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return authHeader["Bearer ".Length..].Trim();
return request.Cookies.TryGetValue(AuthConstants.TokenQueryParamName, out var cookieToken)
? cookieToken
: null;
}
}

View File

@ -68,7 +68,7 @@ public class AuthController(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt, als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt,
new Dictionary<string, object> { { "challenge_id", challenge.Id } }, Request new Dictionary<string, object> { { "challenge_id", challenge.Id } }, Request, account
); );
return challenge; return challenge;
@ -115,7 +115,7 @@ public class AuthController(
[FromBody] PerformChallengeRequest request [FromBody] PerformChallengeRequest request
) )
{ {
var challenge = await db.AuthChallenges.FindAsync(id); var challenge = await db.AuthChallenges.Include(e => e.Account).FirstOrDefaultAsync(e => e.Id == id);
if (challenge is null) return NotFound("Auth challenge was not found."); if (challenge is null) return NotFound("Auth challenge was not found.");
var factor = await db.AccountAuthFactors.FindAsync(request.FactorId); var factor = await db.AccountAuthFactors.FindAsync(request.FactorId);
@ -133,10 +133,11 @@ public class AuthController(
challenge.BlacklistFactors.Add(factor.Id); challenge.BlacklistFactors.Add(factor.Id);
db.Update(challenge); db.Update(challenge);
als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess, als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess,
new Dictionary<string, object> { new Dictionary<string, object>
{
{ "challenge_id", challenge.Id }, { "challenge_id", challenge.Id },
{ "factor_id", factor.Id } { "factor_id", factor.Id }
}, Request }, Request, challenge.Account
); );
} }
else else
@ -149,10 +150,11 @@ public class AuthController(
challenge.FailedAttempts++; challenge.FailedAttempts++;
db.Update(challenge); db.Update(challenge);
als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure, als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure,
new Dictionary<string, object> { new Dictionary<string, object>
{
{ "challenge_id", challenge.Id }, { "challenge_id", challenge.Id },
{ "factor_id", factor.Id } { "factor_id", factor.Id }
}, Request }, Request, challenge.Account
); );
await db.SaveChangesAsync(); await db.SaveChangesAsync();
return BadRequest("Invalid password."); return BadRequest("Invalid password.");
@ -161,10 +163,11 @@ public class AuthController(
if (challenge.StepRemain == 0) if (challenge.StepRemain == 0)
{ {
als.CreateActionLogFromRequest(ActionLogType.NewLogin, als.CreateActionLogFromRequest(ActionLogType.NewLogin,
new Dictionary<string, object> { new Dictionary<string, object>
{
{ "challenge_id", challenge.Id }, { "challenge_id", challenge.Id },
{ "account_id", challenge.AccountId } { "account_id", challenge.AccountId }
}, Request }, Request, challenge.Account
); );
} }

View File

@ -12,6 +12,7 @@ using NetTopologySuite.Geometries;
using NodaTime; using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using NpgsqlTypes; using NpgsqlTypes;
using Point = NetTopologySuite.Geometries.Point;
#nullable disable #nullable disable

View File

@ -137,7 +137,7 @@ public class PostController(
[MaxLength(1024)] public string? Title { get; set; } [MaxLength(1024)] public string? Title { get; set; }
[MaxLength(4096)] public string? Description { get; set; } [MaxLength(4096)] public string? Description { get; set; }
public string? Content { get; set; } public string? Content { get; set; }
public PostVisibility? Visibility { get; set; } public PostVisibility? Visibility { get; set; } = PostVisibility.Public;
public PostType? Type { get; set; } public PostType? Type { get; set; }
[MaxLength(16)] public List<string>? Tags { get; set; } [MaxLength(16)] public List<string>? Tags { get; set; }
[MaxLength(8)] public List<string>? Categories { get; set; } [MaxLength(8)] public List<string>? Categories { get; set; }

View File

@ -54,13 +54,15 @@ builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
}).AddDataAnnotationsLocalization(options => { }).AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResource)); factory.Create(typeof(SharedResource));
}); });
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.Configure<RequestLocalizationOptions>(options => { builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[] var supportedCultures = new[]
{ {
new CultureInfo("en-us"), new CultureInfo("en-us"),
@ -82,21 +84,12 @@ builder.Services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed"
})); }));
builder.Services.AddCors(); builder.Services.AddCors();
builder.Services.AddAuthorization(); builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => builder.Services.AddAuthentication(options =>
{ {
var publicKey = File.ReadAllText(builder.Configuration["Jwt:PublicKeyPath"]!); options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
var rsa = RSA.Create(); options.DefaultChallengeScheme = AuthConstants.SchemeName;
rsa.ImportFromPem(publicKey); })
options.TokenValidationParameters = new TokenValidationParameters .AddScheme<DysonTokenAuthOptions, DysonTokenAuthHandler>(AuthConstants.SchemeName, _ => { });
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "solar-network",
IssuerSigningKey = new RsaSecurityKey(rsa)
};
});
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => builder.Services.AddSwaggerGen(options =>
@ -155,7 +148,7 @@ builder.Services.AddScoped<EmailService>();
builder.Services.AddScoped<PermissionService>(); builder.Services.AddScoped<PermissionService>();
builder.Services.AddScoped<AccountService>(); builder.Services.AddScoped<AccountService>();
builder.Services.AddScoped<AccountEventService>(); builder.Services.AddScoped<AccountEventService>();
builder.Services.AddSingleton<ActionLogService>(); builder.Services.AddScoped<ActionLogService>();
builder.Services.AddScoped<RelationshipService>(); builder.Services.AddScoped<RelationshipService>();
builder.Services.AddScoped<MagicSpellService>(); builder.Services.AddScoped<MagicSpellService>();
builder.Services.AddScoped<NotificationService>(); builder.Services.AddScoped<NotificationService>();
@ -284,14 +277,14 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
using var scope = eventContext.HttpContext.RequestServices.CreateScope(); using var scope = eventContext.HttpContext.RequestServices.CreateScope();
var services = scope.ServiceProvider; var services = scope.ServiceProvider;
try
{
var httpContext = eventContext.HttpContext; var httpContext = eventContext.HttpContext;
if (httpContext.Items["CurrentUser"] is not Account user) return; if (httpContext.Items["CurrentUser"] is not Account user) return;
var file = await eventContext.GetFileAsync(); var file = await eventContext.GetFileAsync();
var metadata = await file.GetMetadataAsync(eventContext.CancellationToken); var metadata = await file.GetMetadataAsync(eventContext.CancellationToken);
var fileName = metadata.TryGetValue("filename", out var fn) ? fn.GetString(Encoding.UTF8) : "uploaded_file"; var fileName = metadata.TryGetValue("filename", out var fn)
? fn.GetString(Encoding.UTF8)
: "uploaded_file";
var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null; var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
var fileStream = await file.GetContentAsync(eventContext.CancellationToken); var fileStream = await file.GetContentAsync(eventContext.CancellationToken);
@ -300,18 +293,14 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType); var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType);
using var finalScope = eventContext.HttpContext.RequestServices.CreateScope(); using var finalScope = eventContext.HttpContext.RequestServices.CreateScope();
var jsonOptions = finalScope.ServiceProvider.GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions; var jsonOptions = finalScope.ServiceProvider.GetRequiredService<IOptions<JsonOptions>>().Value
.JsonSerializerOptions;
var infoJson = JsonSerializer.Serialize(info, jsonOptions); var infoJson = JsonSerializer.Serialize(info, jsonOptions);
eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson); eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson);
// Dispose the stream after all processing is complete // Dispose the stream after all processing is complete
await fileStream.DisposeAsync(); await fileStream.DisposeAsync();
} }
catch (Exception ex)
{
throw;
}
}
} }
})); }));

View File

@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003Ff0_003F595b6eda_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABodyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc5c8aba04a29d49c65d772c9ffcd93ac7eb38ccbb49a5f506518a0b9bdcaa75_003FBodyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABodyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc5c8aba04a29d49c65d772c9ffcd93ac7eb38ccbb49a5f506518a0b9bdcaa75_003FBodyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@ -35,6 +36,8 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aa8ac544afb487082402c1fa422910f2e00_003F7f_003F8e728ed6_003FIStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aa8ac544afb487082402c1fa422910f2e00_003F7f_003F8e728ed6_003FIStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff611a1225a3445458f2ca3f102eed5bdcd10_003F07_003F030df6ba_003FJwtBearerExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtSecurityTokenHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F477051138f1f40de9077b7b1cdc55c6215fb0_003Ff5_003Fd716e016_003FJwtSecurityTokenHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@ -55,6 +58,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F66_003Fde27c365_003FSafeHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F66_003Fde27c365_003FSafeHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fce37be1a06b16c6faa02038d2cc477dd3bca5b217ceeb41c5f2ad45c1bf9_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>