♻️ Rebuilt own auth infra
This commit is contained in:
parent
aabe8269f5
commit
88977ccda3
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) =>
|
||||||
|
77
DysonNetwork.Sphere/Auth/Auth.cs
Normal file
77
DysonNetwork.Sphere/Auth/Auth.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
@ -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"]!);
|
|
||||||
var rsa = RSA.Create();
|
|
||||||
rsa.ImportFromPem(publicKey);
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
|
||||||
ValidateAudience = false,
|
options.DefaultChallengeScheme = AuthConstants.SchemeName;
|
||||||
ValidateLifetime = true,
|
})
|
||||||
ValidateIssuerSigningKey = true,
|
.AddScheme<DysonTokenAuthOptions, DysonTokenAuthHandler>(AuthConstants.SchemeName, _ => { });
|
||||||
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,33 +277,29 @@ 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;
|
||||||
{
|
if (httpContext.Items["CurrentUser"] is not Account user) return;
|
||||||
var httpContext = eventContext.HttpContext;
|
|
||||||
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)
|
||||||
var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
|
? fn.GetString(Encoding.UTF8)
|
||||||
|
: "uploaded_file";
|
||||||
|
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);
|
||||||
|
|
||||||
var fileService = services.GetRequiredService<FileService>();
|
var fileService = services.GetRequiredService<FileService>();
|
||||||
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
|
||||||
var infoJson = JsonSerializer.Serialize(info, jsonOptions);
|
.JsonSerializerOptions;
|
||||||
eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson);
|
var infoJson = JsonSerializer.Serialize(info, jsonOptions);
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user