Update user and folder encryption keys navigation naming (#2159)
This commit is contained in:
@@ -921,7 +921,7 @@ export class VaultSyncService {
|
||||
buckets: bucketDtos,
|
||||
newBlobs: [] as BlobDto[],
|
||||
emailRouting: { emailAddressList },
|
||||
encryptionPublicKey: '',
|
||||
userEncryptionPublicKey: '',
|
||||
};
|
||||
|
||||
let resp = await webApi.post<typeof payload, VaultWriteResponseDto>(VAULT_ENDPOINT, payload);
|
||||
@@ -1148,7 +1148,7 @@ export class VaultSyncService {
|
||||
const webApi = new WebApiService();
|
||||
/** POST the single-bucket write with the given believed-current revision (called again on the rebase retry). */
|
||||
const postBucket = (currentRevision: number): Promise<VaultWriteResponseDto> => webApi.post<Record<string, unknown>, VaultWriteResponseDto>(VAULT_ENDPOINT, {
|
||||
username, manifests: [], buckets: [{ category, blob: ciphertext, ciphertextHash, currentRevision }], newBlobs: [], emailRouting: null, encryptionPublicKey: '',
|
||||
username, manifests: [], buckets: [{ category, blob: ciphertext, ciphertextHash, currentRevision }], newBlobs: [], emailRouting: null, userEncryptionPublicKey: '',
|
||||
});
|
||||
|
||||
let currentRevision = (((await storage.getItem(bucketRevisionStorageKey(category))) as number | null) ?? 0);
|
||||
|
||||
@@ -139,7 +139,7 @@ export async function pushManifest(
|
||||
newBlobs: [],
|
||||
blobReferences,
|
||||
emailRouting: { emailAddressList: [] },
|
||||
encryptionPublicKey: '',
|
||||
userEncryptionPublicKey: '',
|
||||
};
|
||||
|
||||
const response = await fetch(`${apiBaseUrl.replace(/\/$/, '')}/v2/Vault`, {
|
||||
|
||||
@@ -547,9 +547,8 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
private async Task UpdateUserPublicKey(AliasServerDbContext context, string userId, string newPublicKey)
|
||||
{
|
||||
// Get all existing user public keys.
|
||||
var publicKeyExists = await context.UserEncryptionKeys
|
||||
.AnyAsync(x => x.UserId == userId && x.IsPrimary && x.PublicKey == newPublicKey);
|
||||
.AnyAsync(x => x.UserId == userId && x.VaultManifestId == null && x.IsPrimary && x.PublicKey == newPublicKey);
|
||||
|
||||
// If the public key already exists and is marked as primary (default), do nothing.
|
||||
if (publicKeyExists)
|
||||
@@ -557,9 +556,9 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
return;
|
||||
}
|
||||
|
||||
// Update all existing keys to not be primary.
|
||||
// Update all existing personal keys to not be primary.
|
||||
var otherKeys = await context.UserEncryptionKeys
|
||||
.Where(x => x.UserId == userId)
|
||||
.Where(x => x.UserId == userId && x.VaultManifestId == null)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var key in otherKeys)
|
||||
@@ -570,7 +569,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
|
||||
// Check if the new public key already exists but is not marked as primary.
|
||||
var existingPublicKey = await context.UserEncryptionKeys
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId && x.PublicKey == newPublicKey);
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId && x.VaultManifestId == null && x.PublicKey == newPublicKey);
|
||||
|
||||
if (existingPublicKey is not null)
|
||||
{
|
||||
|
||||
@@ -352,6 +352,27 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
.HasForeignKey(l => l.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<UserEncryptionKey>(builder =>
|
||||
{
|
||||
// A folder-scoped key is removed when its manifest is removed; a personal key (null manifest) is unaffected.
|
||||
builder.HasOne(k => k.VaultManifest)
|
||||
.WithMany()
|
||||
.HasForeignKey(k => k.VaultManifestId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Delivery resolves the primary key for a scope on every inbound mail, so index the lookup.
|
||||
builder.HasIndex(k => new { k.UserId, k.VaultManifestId, k.IsPrimary });
|
||||
});
|
||||
|
||||
// Configure UserEmailClaim - UserEncryptionKey relationship. Restrict rather than cascade: an
|
||||
// email claim outlives its key on purpose (claims are retained to prevent address re-use), so a
|
||||
// key must be re-pointed or nulled before it can be removed.
|
||||
modelBuilder.Entity<UserEmailClaim>()
|
||||
.HasOne(c => c.EncryptionKey)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.EncryptionKeyId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Configure MobileLoginRequest - AliasVaultUser relationship
|
||||
modelBuilder.Entity<MobileLoginRequest>()
|
||||
.HasOne(m => m.User)
|
||||
|
||||
@@ -15,6 +15,12 @@ public class EmailRouting
|
||||
/// <summary>Gets or sets the user's claimed email addresses (forwarded inbound).</summary>
|
||||
public List<string> EmailAddressList { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the claimed addresses whose items live in a shared folder, each carrying the manifest
|
||||
/// whose published keypair encrypts its mail.
|
||||
/// </summary>
|
||||
public List<SharedEmailAddress> SharedEmailAddressList { get; set; } = [];
|
||||
|
||||
/// <summary>Gets or sets the private email domains available to this user.</summary>
|
||||
public List<string> PrivateEmailDomainList { get; set; } = [];
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SharedEmailAddress.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Shared.Models.WebApi.V2.Vault;
|
||||
|
||||
/// <summary>
|
||||
/// An email alias whose item lives in a shared folder: mail for it is encrypted with the folder's
|
||||
/// published keypair rather than the routing owner's personal key, so every member of the folder can
|
||||
/// read it.
|
||||
/// </summary>
|
||||
public class SharedEmailAddress
|
||||
{
|
||||
/// <summary>Gets or sets the full email address.</summary>
|
||||
public required string Address { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the shared-folder manifest whose key encrypts mail for this address.</summary>
|
||||
public required Guid ManifestId { get; set; }
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SharedFolderEncryptionPublicKey.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Shared.Models.WebApi.V2.Vault;
|
||||
|
||||
/// <summary>
|
||||
/// The public half of a shared folder's email keypair, published so the SMTP service can encrypt mail
|
||||
/// for the folder's aliases. The private half never leaves the folder's manifest.
|
||||
/// </summary>
|
||||
public class SharedFolderEncryptionPublicKey
|
||||
{
|
||||
/// <summary>Gets or sets the shared-folder manifest this key belongs to.</summary>
|
||||
public required Guid ManifestId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the public key to publish as the folder's active delivery key.</summary>
|
||||
public required string PublicKey { get; set; }
|
||||
}
|
||||
@@ -27,6 +27,12 @@ public class VaultWriteRequest
|
||||
/// <summary>Gets or sets the email routing data to update server-side.</summary>
|
||||
public EmailRouting? EmailRouting { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the public encryption key.</summary>
|
||||
public string? EncryptionPublicKey { get; set; }
|
||||
/// <summary>Gets or sets the public half of the user's own encryption keypair (client table `EncryptionKeys`).</summary>
|
||||
public string? UserEncryptionPublicKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the public halves of the shared folders' own encryption keypairs (client table
|
||||
/// `SharedFolderEncryptionKeys`).
|
||||
/// </summary>
|
||||
public List<SharedFolderEncryptionPublicKey> SharedFolderEncryptionPublicKeys { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ pub static BUCKET_TABLES: &[(&str, &str)] = &[
|
||||
/// are found in a shared manifest anyway.
|
||||
pub static PERSONAL_TABLES: &[&str] = &["EncryptionKeys"];
|
||||
|
||||
/// Tables that belong exclusively to a *shared-folder* manifest and never to the root.
|
||||
// `SharedFolderEncryptionKeys` carries a folder's own email keypair, so that every member
|
||||
/// of the folder can decrypt mail addressed to the folder's aliases; it is encrypted under
|
||||
/// Tables that belong exclusively to a *shared-folder* manifest and never to the root.
|
||||
/// `SharedFolderEncryptionKeys` carries a folder's own email keypair, so that every member
|
||||
/// of the folder can decrypt mail addressed to the folder's aliases; it is encrypted under
|
||||
/// the folder VEK and therefore readable by exactly the folder's members.
|
||||
pub static SHARED_ONLY_TABLES: &[&str] = &["SharedFolderEncryptionKeys"];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user