mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-08-01 01:37:44 -04:00
Update shadow block checks (#2131)
This commit is contained in:
committed by
Leendert de Borst
parent
b6a1880769
commit
b6d7fcc1be
@@ -23,7 +23,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">DbContext instance.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
/// <param name="ipBlockListService">IpBlockListService used to shadow-ban email retrieval from blocked IPs.</param>
|
||||
/// <param name="ipBlockListService">IpBlockListService used to shadow-block email retrieval from blocked IPs.</param>
|
||||
[ApiVersion("1")]
|
||||
public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, UserManager<AliasVaultUser> userManager, IpBlockListService ipBlockListService) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
@@ -43,11 +43,8 @@ public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, U
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
// Shadow-blocked: a shadow-blocked account or blocked IP gets an empty mailbox rather than an explicit error.
|
||||
if (await IsEmailRetrievalShadowBlockedAsync(user))
|
||||
{
|
||||
return Ok(new MailboxApiModel { Address = to, Subscribed = false, Mails = [] });
|
||||
}
|
||||
// Shadow-block: when active, only emails received before the block took effect are visible.
|
||||
var shadowCutoff = await ipBlockListService.GetShadowBlockCutoffAsync(user, IpAddressUtility.GetRawIpAddressFromContext(HttpContext));
|
||||
|
||||
var sanitizedEmail = to.Trim().ToLower();
|
||||
|
||||
@@ -79,9 +76,14 @@ public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, U
|
||||
});
|
||||
}
|
||||
|
||||
// Retrieve emails from database.
|
||||
List<MailboxEmailApiModel> emails = await context.Emails.AsNoTracking()
|
||||
.Where(x => x.To == sanitizedEmail)
|
||||
// Retrieve emails from database (excluding any received after a shadow-block took effect).
|
||||
var emailQuery = context.Emails.AsNoTracking().Where(x => x.To == sanitizedEmail);
|
||||
if (shadowCutoff is not null)
|
||||
{
|
||||
emailQuery = emailQuery.Where(x => x.DateSystem <= shadowCutoff.Value);
|
||||
}
|
||||
|
||||
List<MailboxEmailApiModel> emails = await emailQuery
|
||||
.Select(x => new MailboxEmailApiModel()
|
||||
{
|
||||
Id = x.Id,
|
||||
@@ -128,18 +130,8 @@ public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, U
|
||||
return Unauthorized("Not authenticated.");
|
||||
}
|
||||
|
||||
// Shadow-blocked: a shadow-blocked account or blocked IP gets an empty result rather than an explicit error.
|
||||
if (await IsEmailRetrievalShadowBlockedAsync(user))
|
||||
{
|
||||
return Ok(new MailboxBulkResponse
|
||||
{
|
||||
Addresses = [],
|
||||
Mails = [],
|
||||
PageSize = Math.Min(model.PageSize, 50),
|
||||
CurrentPage = model.Page,
|
||||
TotalRecords = 0,
|
||||
});
|
||||
}
|
||||
// Shadow-block: when active, only emails received before the block took effect are visible.
|
||||
var shadowCutoff = await ipBlockListService.GetShadowBlockCutoffAsync(user, IpAddressUtility.GetRawIpAddressFromContext(HttpContext));
|
||||
|
||||
// Sanitize input.
|
||||
model.Addresses = model.Addresses.Select(x => x.Trim().ToLower()).ToList();
|
||||
@@ -155,6 +147,11 @@ public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, U
|
||||
.AsNoTracking()
|
||||
.Where(email => validAddresses.Contains(email.To));
|
||||
|
||||
if (shadowCutoff is not null)
|
||||
{
|
||||
query = query.Where(email => email.DateSystem <= shadowCutoff.Value);
|
||||
}
|
||||
|
||||
// Get all emails for the valid addresses.
|
||||
var rows = await query
|
||||
.OrderByDescending(x => x.DateSystem)
|
||||
@@ -196,13 +193,4 @@ public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, U
|
||||
|
||||
return Ok(returnValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether email retrieval for the current request should be shadow-blocked, either because the
|
||||
/// account is shadow-blocked or because the request originates from a shadow-blocked IP range.
|
||||
/// </summary>
|
||||
/// <param name="user">The authenticated user.</param>
|
||||
/// <returns>True if email retrieval should return an empty result.</returns>
|
||||
private async Task<bool> IsEmailRetrievalShadowBlockedAsync(AliasVaultUser user)
|
||||
=> user.ShadowBlocked || await ipBlockListService.IsBlockedForEmailsAsync(IpAddressUtility.GetRawIpAddressFromContext(HttpContext));
|
||||
}
|
||||
|
||||
@@ -153,11 +153,8 @@ public class EmailController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
return (null, Unauthorized("Not authenticated."));
|
||||
}
|
||||
|
||||
// Shadow-blocked: a shadow-blocked account or blocked IP behaves as if the email does not exist.
|
||||
if (user.ShadowBlocked || await ipBlockListService.IsBlockedForEmailsAsync(IpAddressUtility.GetRawIpAddressFromContext(HttpContext)))
|
||||
{
|
||||
return (null, NotFound());
|
||||
}
|
||||
// Shadow-block: when active, emails received after the block took effect behave as if they do not exist.
|
||||
var shadowCutoff = await ipBlockListService.GetEmailShadowBlockCutoffAsync(user, IpAddressUtility.GetRawIpAddressFromContext(HttpContext));
|
||||
|
||||
// Retrieve email from database.
|
||||
var email = await context.Emails
|
||||
@@ -169,6 +166,12 @@ public class EmailController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
return (null, NotFound("Email not found."));
|
||||
}
|
||||
|
||||
// Hide emails received after a shadow-block took effect.
|
||||
if (shadowCutoff is not null && email.DateSystem > shadowCutoff.Value)
|
||||
{
|
||||
return (null, NotFound());
|
||||
}
|
||||
|
||||
// See if this user has a valid claim to the email address.
|
||||
var normalizedEmailAddress = email.To.Trim().ToLower();
|
||||
var emailClaim = await context.UserEmailClaims.FirstOrDefaultAsync(x => x.UserId == user.Id && x.Address == normalizedEmailAddress && !x.Disabled);
|
||||
|
||||
@@ -41,12 +41,12 @@ public class IpBlockListService(IAliasServerDbContextFactory dbContextFactory)
|
||||
/// <param name="user">The authenticated user.</param>
|
||||
/// <param name="ipAddress">The IP address to evaluate.</param>
|
||||
/// <returns>The earliest shadow-block timestamp, or null when not shadow-blocked.</returns>
|
||||
public async Task<DateTime?> GetEmailShadowBlockCutoffAsync(AliasVaultUser user, IPAddress? ipAddress)
|
||||
public async Task<DateTime?> GetShadowBlockCutoffAsync(AliasVaultUser user, IPAddress? ipAddress)
|
||||
{
|
||||
// Account-level shadow-block. When the timestamp is unknown, return min timestamp.
|
||||
DateTime? cutoff = user.ShadowBlocked ? (user.ShadowBlockedAt ?? DateTime.UnixEpoch) : null;
|
||||
|
||||
// IP-range shadow-block: the earliest matching block determines the cutoff (the most that should be hidden).
|
||||
// IP-range shadow-block: the earliest matching block determines the cutoff time.
|
||||
if (ipAddress is not null)
|
||||
{
|
||||
await using var dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
Reference in New Issue
Block a user