//-----------------------------------------------------------------------
//
// Copyright (c) aliasvault. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
//
//-----------------------------------------------------------------------
namespace AliasVault.Api.Controllers.Email;
using AliasServerDb;
using AliasVault.Api.Controllers.Abstracts;
using AliasVault.Shared.Models.Spamok;
using AliasVault.Shared.Models.WebApi;
using AliasVault.Shared.Models.WebApi.Email;
using Asp.Versioning;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
///
/// Email controller for retrieving emailboxes from the database.
///
/// DbContext instance.
/// UserManager instance.
[ApiVersion("1")]
public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager)
{
///
/// Returns a list of emails for the provided email address.
///
/// The full email address including @ sign.
/// List of aliases in JSON format.
[HttpGet(template: "{to}", Name = "GetEmailBox")]
public async Task GetEmailBox(string to)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
var user = await GetCurrentUserAsync();
if (user is null)
{
return Unauthorized("Not authenticated.");
}
var sanitizedEmail = to.Trim().ToLower();
// See if this user has a valid claim to the email address.
var emailClaim = await context.UserEmailClaims
.FirstOrDefaultAsync(x => x.Address == sanitizedEmail);
if (emailClaim is null)
{
return BadRequest(new ApiErrorResponse
{
Message = "No claim exists for this email address.",
Code = "CLAIM_DOES_NOT_EXIST",
Details = new { ProvidedEmail = sanitizedEmail },
StatusCode = StatusCodes.Status400BadRequest,
Timestamp = DateTime.UtcNow,
});
}
if (emailClaim.UserId != user.Id)
{
return BadRequest(new ApiErrorResponse
{
Message = "Claim does not match user.",
Code = "CLAIM_DOES_NOT_MATCH_USER",
Details = new { ProvidedEmail = to },
StatusCode = StatusCodes.Status400BadRequest,
Timestamp = DateTime.UtcNow,
});
}
// Retrieve emails from database.
List emails = await context.Emails.AsNoTracking()
.Where(x => x.To == sanitizedEmail)
.Select(x => new MailboxEmailApiModel()
{
Id = x.Id,
Subject = x.Subject,
FromDisplay = x.From,
FromDomain = x.FromDomain,
FromLocal = x.FromLocal,
ToDomain = x.ToDomain,
ToLocal = x.ToLocal,
Date = DateTime.SpecifyKind(x.Date, DateTimeKind.Utc),
DateSystem = DateTime.SpecifyKind(x.DateSystem, DateTimeKind.Utc),
SecondsAgo = (int)DateTime.UtcNow.Subtract(x.DateSystem).TotalSeconds,
MessagePreview = x.MessagePreview ?? string.Empty,
EncryptedSymmetricKey = x.EncryptedSymmetricKey,
EncryptionKey = x.EncryptionKey.PublicKey,
})
.OrderByDescending(x => x.DateSystem)
.Take(50)
.ToListAsync();
var returnValue = new MailboxApiModel
{
Address = to,
Subscribed = false,
Mails = emails,
};
return Ok(returnValue);
}
///
/// Returns a list of emails for the provided list of email addresses.
///
/// The request model extracted from POST body.
/// List of emails in JSON format.
[HttpPost(template: "bulk", Name = "GetEmailBoxBulk")]
public async Task GetEmailBoxBulk([FromBody] MailboxBulkRequest model)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
var user = await GetCurrentUserAsync();
if (user is null)
{
return Unauthorized("Not authenticated.");
}
// Sanitize input.
model.Addresses = model.Addresses.Select(x => x.Trim().ToLower()).ToList();
model.PageSize = Math.Min(model.PageSize, 50);
// Load all email addresses that the user has a claim to where the address is in the list.
var emailClaims = await context.UserEmailClaims
.Where(claim => claim.UserId == user.Id && model.Addresses.Contains(claim.Address))
.ToListAsync();
var query = context.Emails
.AsNoTracking()
.Include(x => x.EncryptionKey)
.Where(email => context.UserEmailClaims
.Any(claim => claim.UserId == user.Id
&& claim.Address == email.To
&& model.Addresses.Contains(claim.Address)));
var totalRecords = await query.CountAsync();
List emails = await query.Select(x => new MailboxEmailApiModel
{
Id = x.Id,
Subject = x.Subject,
FromDisplay = x.From,
FromDomain = x.FromDomain,
FromLocal = x.FromLocal,
ToDomain = x.ToDomain,
ToLocal = x.ToLocal,
Date = DateTime.SpecifyKind(x.Date, DateTimeKind.Utc),
DateSystem = DateTime.SpecifyKind(x.DateSystem, DateTimeKind.Utc),
SecondsAgo = (int)DateTime.UtcNow.Subtract(x.DateSystem).TotalSeconds,
MessagePreview = x.MessagePreview ?? string.Empty,
EncryptedSymmetricKey = x.EncryptedSymmetricKey,
EncryptionKey = x.EncryptionKey.PublicKey,
HasAttachments = x.Attachments.Any(),
})
.OrderByDescending(x => x.DateSystem)
.Skip((model.Page - 1) * model.PageSize)
.Take(model.PageSize)
.ToListAsync();
MailboxBulkResponse returnValue = new()
{
Addresses = emailClaims.Select(x => x.Address).ToList(),
Mails = emails,
PageSize = model.PageSize,
CurrentPage = model.Page,
TotalRecords = totalRecords,
};
return Ok(returnValue);
}
}