Add PKI tables (#117)

This commit is contained in:
Leendert de Borst
2024-07-29 11:18:38 +02:00
parent 7fd2b9d678
commit 2016117d47
16 changed files with 1255 additions and 9 deletions

View File

@@ -65,10 +65,10 @@ public class VaultController(IDbContextFactory<AliasServerDbContext> dbContextFa
// as starting point.
if (vault == null)
{
return Ok(new Shared.Models.WebApi.Vault(string.Empty, string.Empty, DateTime.MinValue, DateTime.MinValue));
return Ok(new Shared.Models.WebApi.Vault(string.Empty, string.Empty, new List<string>(), DateTime.MinValue, DateTime.MinValue));
}
return Ok(new Shared.Models.WebApi.Vault(vault.VaultBlob, vault.Version, vault.CreatedAt, vault.UpdatedAt));
return Ok(new Shared.Models.WebApi.Vault(vault.VaultBlob, vault.Version, new List<string>(), vault.CreatedAt, vault.UpdatedAt));
}
/// <summary>
@@ -116,6 +116,51 @@ public class VaultController(IDbContextFactory<AliasServerDbContext> dbContextFa
await context.Vaults.AddAsync(newVault);
await context.SaveChangesAsync();
// Update user email claims if email addresses have been supplied.
if (model.EmailAddressList.Count > 0)
{
await UpdateUserEmailClaims(context, user.Id, model.EmailAddressList);
}
return Ok();
}
/// <summary>
/// Updates the user's email claims based on the provided email address list.
/// </summary>
/// <param name="context">The database context.</param>
/// <param name="userId">The ID of the user.</param>
/// <param name="newEmailAddresses">The list of new email addresses to claim.</param>
/// <returns>A task representing the asynchronous operation.</returns>
private async Task UpdateUserEmailClaims(AliasServerDbContext context, string userId, List<string> newEmailAddresses)
{
// Get all existing user email claims.
var existingEmailClaims = await context.UserEmailClaims
.Where(x => x.UserId == userId)
.Select(x => x.Address)
.ToListAsync();
// Register new email addresses.
foreach (var email in newEmailAddresses)
{
if (!existingEmailClaims.Contains(email))
{
await context.UserEmailClaims.AddAsync(new UserEmailClaim
{
UserId = userId,
Address = email,
AddressLocal = email.Split('@')[0],
AddressDomain = email.Split('@')[1],
CreatedAt = timeProvider.UtcNow,
UpdatedAt = timeProvider.UtcNow,
});
}
}
// Do not delete email claims that are not in the new list
// as they may be re-used by the user in the future. We don't want
// to allow other users to re-use emails used by other users.
// Email claims are considered permanent.
await context.SaveChangesAsync();
}
}

View File

@@ -0,0 +1,27 @@
//-----------------------------------------------------------------------
// <copyright file="Config.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Client;
/// <summary>
/// Configuration class for the Client project with values loaded from appsettings.json.
/// </summary>
public class Config
{
/// <summary>
/// Gets or sets the admin password hash which is generated by install.sh and will be set
/// as the default password for the admin user.
/// </summary>
public string ApiUrl { get; set; } = "false";
/// <summary>
/// Gets or sets the domains that the AliasVault server is listening for.
/// Email addresses that client vault users use will be registered at the server
/// to get exclusive access to the email address.
/// </summary>
public List<string> SmtpAllowedDomains { get; set; } = [];
}

View File

@@ -16,6 +16,20 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddJsonFile($"appsettings.{builder.HostEnvironment.Environment}.json", optional: true, reloadOnChange: true);
var config = new Config();
builder.Configuration.Bind(config);
if (string.IsNullOrEmpty(config.ApiUrl))
{
throw new KeyNotFoundException("ApiUrl is not set in the configuration.");
}
if (config.SmtpAllowedDomains == null || config.SmtpAllowedDomains.Count == 0)
{
throw new KeyNotFoundException("SmtpAllowedDomains is not set in the configuration.");
}
builder.Services.AddSingleton(config);
builder.Services.AddLogging(logging =>
{
if (builder.HostEnvironment.IsDevelopment())

View File

@@ -27,6 +27,7 @@ public class DbService : IDisposable
private readonly IJSRuntime _jsRuntime;
private readonly HttpClient _httpClient;
private readonly DbServiceState _state = new();
private readonly Config _config;
private SqliteConnection _sqlConnection;
private AliasClientDbContext _dbContext;
private bool _isSuccessfullyInitialized;
@@ -39,11 +40,13 @@ public class DbService : IDisposable
/// <param name="authService">AuthService.</param>
/// <param name="jsRuntime">IJSRuntime.</param>
/// <param name="httpClient">HttpClient.</param>
public DbService(AuthService authService, IJSRuntime jsRuntime, HttpClient httpClient)
/// <param name="config">Config instance.</param>
public DbService(AuthService authService, IJSRuntime jsRuntime, HttpClient httpClient, Config config)
{
_authService = authService;
_jsRuntime = jsRuntime;
_httpClient = httpClient;
_config = config;
// Set the initial state of the database service.
_state.UpdateState(DbServiceState.DatabaseStatus.Uninitialized);
@@ -441,8 +444,18 @@ public class DbService : IDisposable
/// <returns>True if save action succeeded.</returns>
private async Task<bool> SaveToServerAsync(string encryptedDatabase)
{
// Send list of email addresses that are used in aliases by this vault so they can be
// claimed on the server.
var emailAddresses = await _dbContext.Aliases
.Where(a => a.Email != null)
.Select(a => a.Email)
.Where(email => _config.SmtpAllowedDomains.Any(domain => EF.Functions.Like(email, $"%@{domain}")))
.Distinct()
.Select(email => email!)
.ToListAsync();
var databaseVersion = await GetCurrentDatabaseVersionAsync();
var vaultObject = new Vault(encryptedDatabase, databaseVersion, DateTime.Now, DateTime.Now);
var vaultObject = new Vault(encryptedDatabase, databaseVersion, emailAddresses, DateTime.Now, DateTime.Now);
try
{

View File

@@ -1,12 +1,18 @@
#!/bin/sh
# Set the default API URL for localhost debugging
DEFAULT_API_URL="http://localhost:81"
DEFAULT_SMTP_ALLOWED_DOMAINS="localmail.tld"
# Use the provided API_URL environment variable if it exists, otherwise use the default
API_URL=${API_URL:-$DEFAULT_API_URL}
SMTP_ALLOWED_DOMAINS=${SMTP_ALLOWED_DOMAINS:-$DEFAULT_SMTP_ALLOWED_DOMAINS}
# Replace the default URL with the actual API URL
sed -i "s|http://localhost:5092|${API_URL}|g" /usr/share/nginx/html/appsettings.json
# Replace the default SMTP allowed domains with the actual allowed SMTP domains
# Note: this is used so the client knows which email addresses should be registered with the AliasVault server
# in order to be able to receive emails.
sed -i "s|localmail.tld|${SMTP_ALLOWED_DOMAINS}|g" /usr/share/nginx/html/appsettings.json
# Start the application
nginx -g "daemon off;"

View File

@@ -1,3 +1,4 @@
{
"ApiUrl": "http://localhost:5092"
"ApiUrl": "http://localhost:5092",
"SmtpAllowedDomains": "localmail.tld"
}

View File

@@ -17,12 +17,14 @@ public class Vault
/// </summary>
/// <param name="blob">Blob.</param>
/// <param name="version">Version of the vault data model (migration).</param>
/// <param name="emailAddressList">List of email addresses that are used in the vault and should be registered.</param>
/// <param name="createdAt">CreatedAt.</param>
/// <param name="updatedAt">UpdatedAt.</param>
public Vault(string blob, string version, DateTime createdAt, DateTime updatedAt)
public Vault(string blob, string version, List<string> emailAddressList, DateTime createdAt, DateTime updatedAt)
{
Blob = blob;
Version = version;
EmailAddressList = emailAddressList;
CreatedAt = createdAt;
UpdatedAt = updatedAt;
}
@@ -37,6 +39,11 @@ public class Vault
/// </summary>
public string Version { get; set; }
/// <summary>
/// Gets or sets the list of email addresses that are used in the vault and should be registered on the server.
/// </summary>
public List<string> EmailAddressList { get; set; }
/// <summary>
/// Gets or sets the date and time of creation.
/// </summary>

View File

@@ -100,6 +100,16 @@ public class AliasServerDbContext : WorkerStatusDbContext
/// </summary>
public DbSet<EmailAttachment> EmailAttachments { get; set; }
/// <summary>
/// Gets or sets the UserEmailClaims DbSet.
/// </summary>
public DbSet<UserEmailClaim> UserEmailClaims { get; set; }
/// <summary>
/// Gets or sets the UserEncryptionKeys DbSet.
/// </summary>
public DbSet<UserEncryptionKey> UserEncryptionKeys { get; set; }
/// <summary>
/// Gets or sets the Logs DbSet.
/// </summary>
@@ -182,6 +192,27 @@ public class AliasServerDbContext : WorkerStatusDbContext
.WithMany(c => c.Vaults)
.HasForeignKey(l => l.UserId)
.OnDelete(DeleteBehavior.Cascade);
// Configure UserEmailClaim - AliasVaultUser relationship
modelBuilder.Entity<UserEmailClaim>()
.HasOne(l => l.User)
.WithMany(c => c.EmailClaims)
.HasForeignKey(l => l.UserId)
.OnDelete(DeleteBehavior.Cascade);
// Configure Email - UserEncryptionKey relationship
modelBuilder.Entity<Email>()
.HasOne(l => l.EncryptionKey)
.WithMany(c => c.Emails)
.HasForeignKey(l => l.UserEncryptionKeyId)
.OnDelete(DeleteBehavior.NoAction);
// Configure UserEncryptionKey - AliasVaultUser relationship
modelBuilder.Entity<UserEncryptionKey>()
.HasOne(l => l.User)
.WithMany(c => c.EncryptionKeys)
.HasForeignKey(l => l.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
/// <summary>

View File

@@ -27,6 +27,13 @@ public class AliasVaultUser : IdentityUser
[StringLength(1000)]
public string Verifier { get; set; } = null!;
/// <summary>
/// Gets or sets the user public key to be used by server to encrypt information that server
/// receives for user such as emails.
/// </summary>
[StringLength(2000)]
public string PublicKey { get; set; } = null!;
/// <summary>
/// Gets or sets created timestamp.
/// </summary>
@@ -41,4 +48,14 @@ public class AliasVaultUser : IdentityUser
/// Gets or sets the collection of vaults.
/// </summary>
public virtual ICollection<Vault> Vaults { get; set; } = [];
/// <summary>
/// Gets or sets the collection of EmailClaims.
/// </summary>
public virtual ICollection<UserEmailClaim> EmailClaims { get; set; } = [];
/// <summary>
/// Gets or sets the collection of EncryptionKeys.
/// </summary>
public virtual ICollection<UserEncryptionKey> EncryptionKeys { get; set; } = [];
}

View File

@@ -7,6 +7,8 @@
namespace AliasServerDb;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
/// <summary>
@@ -24,6 +26,18 @@ public class Email
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets encryption key foreign key.
/// </summary>
[StringLength(255)]
public Guid UserEncryptionKeyId { get; set; }
/// <summary>
/// Gets or sets foreign key to the UserEncryptionKey object.
/// </summary>
[ForeignKey("UserEncryptionKeyId")]
public virtual UserEncryptionKey EncryptionKey { get; set; } = null!;
/// <summary>
/// Gets or sets the subject of the email.
/// </summary>

View File

@@ -0,0 +1,702 @@
// <auto-generated />
using System;
using AliasServerDb;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace AliasServerDb.Migrations
{
[DbContext(typeof(AliasServerDbContext))]
[Migration("20240729090556_AddEncryptionKeyTables")]
partial class AddEncryptionKeyTables
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.7")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true);
modelBuilder.Entity("AliasServerDb.AdminRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("AdminRoles");
});
modelBuilder.Entity("AliasServerDb.AdminUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastPasswordChanged")
.HasColumnType("TEXT");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("AdminUsers");
});
modelBuilder.Entity("AliasServerDb.AliasVaultRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("AliasVaultRoles");
});
modelBuilder.Entity("AliasServerDb.AliasVaultUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("PublicKey")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserName")
.HasColumnType("TEXT");
b.Property<string>("Verifier")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("AliasVaultUsers");
});
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("DeviceIdentifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime>("ExpireDate")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AliasVaultUserRefreshTokens");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Date")
.HasColumnType("TEXT");
b.Property<DateTime>("DateSystem")
.HasColumnType("TEXT");
b.Property<string>("From")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FromDomain")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FromLocal")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("MessageHtml")
.HasColumnType("TEXT");
b.Property<string>("MessagePlain")
.HasColumnType("TEXT");
b.Property<string>("MessagePreview")
.HasColumnType("TEXT");
b.Property<string>("MessageSource")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("PushNotificationSent")
.HasColumnType("INTEGER");
b.Property<string>("Subject")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("To")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ToDomain")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ToLocal")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserEncryptionKeyId")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("Visible")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Date");
b.HasIndex("DateSystem");
b.HasIndex("PushNotificationSent");
b.HasIndex("ToLocal");
b.HasIndex("UserEncryptionKeyId");
b.HasIndex("Visible");
b.ToTable("Emails");
});
modelBuilder.Entity("AliasServerDb.EmailAttachment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<byte[]>("Bytes")
.IsRequired()
.HasColumnType("BLOB");
b.Property<DateTime>("Date")
.HasColumnType("TEXT");
b.Property<int>("EmailId")
.HasColumnType("INTEGER");
b.Property<string>("Filename")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Filesize")
.HasColumnType("INTEGER");
b.Property<string>("MimeType")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EmailId");
b.ToTable("EmailAttachments");
});
modelBuilder.Entity("AliasServerDb.Log", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Application")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Exception")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Level")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("LogEvent")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("LogEvent");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("MessageTemplate")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Properties")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("TimeStamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Application");
b.HasIndex("TimeStamp");
b.ToTable("Logs", (string)null);
});
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Address")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AddressDomain")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AddressLocal")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserEmailClaims");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("PublicKey")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserEncryptionKeys");
});
modelBuilder.Entity("AliasServerDb.Vault", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<int>("FileSize")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("VaultBlob")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Vaults");
});
modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CurrentStatus")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("DesiredStatus")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("Heartbeat")
.HasColumnType("TEXT");
b.Property<string>("ServiceName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar");
b.HasKey("Id");
b.ToTable("WorkerServiceStatuses");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("RoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("UserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.ToTable("UserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("UserTokens", (string)null);
});
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey")
.WithMany("Emails")
.HasForeignKey("UserEncryptionKeyId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("EncryptionKey");
});
modelBuilder.Entity("AliasServerDb.EmailAttachment", b =>
{
b.HasOne("AliasServerDb.Email", "Email")
.WithMany("Attachments")
.HasForeignKey("EmailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Email");
});
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany("EmailClaims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany("EncryptionKeys")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.Vault", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany("Vaults")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.AliasVaultUser", b =>
{
b.Navigation("EmailClaims");
b.Navigation("EncryptionKeys");
b.Navigation("Vaults");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.Navigation("Attachments");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.Navigation("Emails");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,126 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AliasServerDb.Migrations
{
/// <inheritdoc />
public partial class AddEncryptionKeyTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Delete all records from the Email table as adding PKI will break the existing data.
migrationBuilder.Sql("DELETE FROM Emails");
migrationBuilder.AddColumn<Guid>(
name: "UserEncryptionKeyId",
table: "Emails",
type: "TEXT",
maxLength: 255,
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn<string>(
name: "PublicKey",
table: "AliasVaultUsers",
type: "TEXT",
maxLength: 2000,
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "UserEmailClaims",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
UserId = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
Address = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
AddressLocal = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
AddressDomain = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserEmailClaims", x => x.Id);
table.ForeignKey(
name: "FK_UserEmailClaims_AliasVaultUsers_UserId",
column: x => x.UserId,
principalTable: "AliasVaultUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserEncryptionKeys",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
UserId = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
PublicKey = table.Column<string>(type: "TEXT", maxLength: 2000, nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserEncryptionKeys", x => x.Id);
table.ForeignKey(
name: "FK_UserEncryptionKeys_AliasVaultUsers_UserId",
column: x => x.UserId,
principalTable: "AliasVaultUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Emails_UserEncryptionKeyId",
table: "Emails",
column: "UserEncryptionKeyId");
migrationBuilder.CreateIndex(
name: "IX_UserEmailClaims_UserId",
table: "UserEmailClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_UserEncryptionKeys_UserId",
table: "UserEncryptionKeys",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_Emails_UserEncryptionKeys_UserEncryptionKeyId",
table: "Emails",
column: "UserEncryptionKeyId",
principalTable: "UserEncryptionKeys",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Emails_UserEncryptionKeys_UserEncryptionKeyId",
table: "Emails");
migrationBuilder.DropTable(
name: "UserEmailClaims");
migrationBuilder.DropTable(
name: "UserEncryptionKeys");
migrationBuilder.DropIndex(
name: "IX_Emails_UserEncryptionKeyId",
table: "Emails");
migrationBuilder.DropColumn(
name: "UserEncryptionKeyId",
table: "Emails");
migrationBuilder.DropColumn(
name: "PublicKey",
table: "AliasVaultUsers");
}
}
}

View File

@@ -155,6 +155,11 @@ namespace AliasServerDb.Migrations
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("PublicKey")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(100)
@@ -273,6 +278,10 @@ namespace AliasServerDb.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserEncryptionKeyId")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("Visible")
.HasColumnType("INTEGER");
@@ -286,6 +295,8 @@ namespace AliasServerDb.Migrations
b.HasIndex("ToLocal");
b.HasIndex("UserEncryptionKeyId");
b.HasIndex("Visible");
b.ToTable("Emails");
@@ -374,6 +385,74 @@ namespace AliasServerDb.Migrations
b.ToTable("Logs", (string)null);
});
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Address")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AddressDomain")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AddressLocal")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserEmailClaims");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("PublicKey")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserEncryptionKeys");
});
modelBuilder.Entity("AliasServerDb.Vault", b =>
{
b.Property<Guid>("Id")
@@ -541,6 +620,17 @@ namespace AliasServerDb.Migrations
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey")
.WithMany("Emails")
.HasForeignKey("UserEncryptionKeyId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("EncryptionKey");
});
modelBuilder.Entity("AliasServerDb.EmailAttachment", b =>
{
b.HasOne("AliasServerDb.Email", "Email")
@@ -552,10 +642,10 @@ namespace AliasServerDb.Migrations
b.Navigation("Email");
});
modelBuilder.Entity("AliasServerDb.Vault", b =>
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany()
.WithMany("EmailClaims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -563,10 +653,46 @@ namespace AliasServerDb.Migrations
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany("EncryptionKeys")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.Vault", b =>
{
b.HasOne("AliasServerDb.AliasVaultUser", "User")
.WithMany("Vaults")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("AliasServerDb.AliasVaultUser", b =>
{
b.Navigation("EmailClaims");
b.Navigation("EncryptionKeys");
b.Navigation("Vaults");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.Navigation("Attachments");
});
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
{
b.Navigation("Emails");
});
#pragma warning restore 612, 618
}
}

View File

@@ -0,0 +1,62 @@
//-----------------------------------------------------------------------
// <copyright file="UserEmailClaim.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasServerDb;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
/// <summary>
/// UserEmailClaim object. This object is used to reserve an email address for a user.
/// </summary>
public class UserEmailClaim
{
/// <summary>
/// Gets or sets the ID.
/// </summary>
[Key]
public Guid Id { get; set; }
/// <summary>
/// Gets or sets user ID foreign key.
/// </summary>
[StringLength(255)]
public string UserId { get; set; } = null!;
/// <summary>
/// Gets or sets foreign key to the AliasVaultUser object.
/// </summary>
[ForeignKey("UserId")]
public virtual AliasVaultUser User { get; set; } = null!;
/// <summary>
/// Gets or sets the full email address.
/// </summary>
[StringLength(255)]
public string Address { get; set; } = null!;
/// <summary>
/// Gets or sets the email adress local part.
/// </summary>
[StringLength(255)]
public string AddressLocal { get; set; } = null!;
/// <summary>
/// Gets or sets the email adress domain part.
/// </summary>
[StringLength(255)]
public string AddressDomain { get; set; } = null!;
/// <summary>
/// Gets or sets created timestamp.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets updated timestamp.
/// </summary>
public DateTime UpdatedAt { get; set; }
}

View File

@@ -0,0 +1,55 @@
//-----------------------------------------------------------------------
// <copyright file="UserEncryptionKey.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasServerDb;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
/// <summary>
/// UserEncryptionKey object. This object is used for storing user public keys for encryption.
/// </summary>
public class UserEncryptionKey
{
/// <summary>
/// Gets or sets the ID.
/// </summary>
[Key]
public Guid Id { get; set; }
/// <summary>
/// Gets or sets user ID foreign key.
/// </summary>
[StringLength(255)]
public string UserId { get; set; } = null!;
/// <summary>
/// Gets or sets foreign key to the AliasVaultUser object.
/// </summary>
[ForeignKey("UserId")]
public virtual AliasVaultUser User { get; set; } = null!;
/// <summary>
/// Gets or sets the public key.
/// </summary>
[StringLength(2000)]
public string PublicKey { get; set; } = null!;
/// <summary>
/// Gets or sets created timestamp.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets updated timestamp.
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// Gets or sets the collection of Emails that are using this encryption key.
/// </summary>
public virtual ICollection<Email> Emails { get; set; } = [];
}

View File

@@ -21,5 +21,5 @@ public class Config
/// Gets or sets the domains that the SMTP service is listening for.
/// Domains not in this list will be rejected.
/// </summary>
public List<String> AllowedToDomains { get; set; } = [];
public List<string> AllowedToDomains { get; set; } = [];
}