diff --git a/apps/server/AliasVault.Admin/Main/Models/MobileLoginRequestWithUsername.cs b/apps/server/AliasVault.Admin/Main/Models/MobileLoginRequestWithUsername.cs
new file mode 100644
index 000000000..460237926
--- /dev/null
+++ b/apps/server/AliasVault.Admin/Main/Models/MobileLoginRequestWithUsername.cs
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------
+//
+// 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.Admin.Main.Models;
+
+using AliasServerDb;
+
+///
+/// View model for MobileLoginRequest joined with User to get username.
+///
+public class MobileLoginRequestWithUsername
+{
+ ///
+ /// Gets or sets the mobile login request.
+ ///
+ public required MobileLoginRequest Request { get; set; }
+
+ ///
+ /// Gets or sets the username from the User table via UserId FK.
+ ///
+ public string? Username { get; set; }
+}
diff --git a/apps/server/AliasVault.Admin/Main/Pages/MobileLoginHistory.razor b/apps/server/AliasVault.Admin/Main/Pages/MobileLoginHistory.razor
index 040e0c542..18044ea16 100644
--- a/apps/server/AliasVault.Admin/Main/Pages/MobileLoginHistory.razor
+++ b/apps/server/AliasVault.Admin/Main/Pages/MobileLoginHistory.razor
@@ -1,4 +1,5 @@
@page "/mobile-login-history"
+@using AliasVault.Admin.Main.Models
@using AliasVault.RazorComponents.Tables
@using Microsoft.AspNetCore.WebUtilities
@inject NavigationManager NavigationManager
@@ -91,15 +92,15 @@ else
@if (request.RetrievedAt.HasValue)
{
-
+
}
else if (request.FulfilledAt.HasValue)
{
-
+
}
else
{
-
+
}
@@ -209,7 +210,16 @@ else
StateHasChanged();
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);
- IQueryable query = dbContext.MobileLoginRequests;
+
+ // Join with AliasVaultUsers table to get username via UserId FK
+ var query = from request in dbContext.MobileLoginRequests
+ join user in dbContext.AliasVaultUsers on request.UserId equals user.Id into userJoin
+ from user in userJoin.DefaultIfEmpty()
+ select new MobileLoginRequestWithUsername
+ {
+ Request = request,
+ Username = user != null ? user.UserName : null
+ };
query = ApplySearchFilter(query);
query = ApplyStatusFilter(query);
@@ -221,13 +231,13 @@ else
.Take(PageSize)
.Select(r => new
{
- r.CreatedAt,
- r.ClientIpAddress,
- r.MobileIpAddress,
- r.FulfilledAt,
- r.RetrievedAt,
+ r.Request.CreatedAt,
+ r.Request.ClientIpAddress,
+ r.Request.MobileIpAddress,
+ r.Request.FulfilledAt,
+ r.Request.RetrievedAt,
r.Username,
- r.UserId
+ r.Request.UserId
})
.ToListAsync(cancellationToken);
@@ -257,7 +267,7 @@ else
}
}
- private IQueryable ApplySearchFilter(IQueryable query)
+ private IQueryable ApplySearchFilter(IQueryable query)
{
if (SearchTerm.Length > 0)
{
@@ -271,15 +281,15 @@ else
var searchTerm = SearchTerm.Trim().ToLower();
query = query.Where(r =>
(r.Username != null && EF.Functions.Like(r.Username.ToLower(), "%" + searchTerm + "%")) ||
- (r.ClientIpAddress != null && EF.Functions.Like(r.ClientIpAddress.ToLower(), "%" + searchTerm + "%")) ||
- (r.MobileIpAddress != null && EF.Functions.Like(r.MobileIpAddress.ToLower(), "%" + searchTerm + "%"))
+ (r.Request.ClientIpAddress != null && EF.Functions.Like(r.Request.ClientIpAddress.ToLower(), "%" + searchTerm + "%")) ||
+ (r.Request.MobileIpAddress != null && EF.Functions.Like(r.Request.MobileIpAddress.ToLower(), "%" + searchTerm + "%"))
);
}
return query;
}
- private IQueryable ApplyStatusFilter(IQueryable query)
+ private IQueryable ApplyStatusFilter(IQueryable query)
{
if (!string.IsNullOrEmpty(SelectedStatusFilter))
{
@@ -293,13 +303,13 @@ else
switch (SelectedStatusFilter)
{
case "retrieved":
- query = query.Where(r => r.RetrievedAt != null);
+ query = query.Where(r => r.Request.RetrievedAt != null);
break;
case "fulfilled":
- query = query.Where(r => r.FulfilledAt != null && r.RetrievedAt == null);
+ query = query.Where(r => r.Request.FulfilledAt != null && r.Request.RetrievedAt == null);
break;
case "pending":
- query = query.Where(r => r.FulfilledAt == null);
+ query = query.Where(r => r.Request.FulfilledAt == null);
break;
}
}
@@ -307,34 +317,34 @@ else
return query;
}
- private IQueryable ApplySort(IQueryable query)
+ private IQueryable ApplySort(IQueryable query)
{
switch (SortColumn)
{
case "CreatedAt":
query = SortDirection == SortDirection.Ascending
- ? query.OrderBy(x => x.CreatedAt)
- : query.OrderByDescending(x => x.CreatedAt);
+ ? query.OrderBy(x => x.Request.CreatedAt)
+ : query.OrderByDescending(x => x.Request.CreatedAt);
break;
case "ClientIpAddress":
query = SortDirection == SortDirection.Ascending
- ? query.OrderBy(x => x.ClientIpAddress)
- : query.OrderByDescending(x => x.ClientIpAddress);
+ ? query.OrderBy(x => x.Request.ClientIpAddress)
+ : query.OrderByDescending(x => x.Request.ClientIpAddress);
break;
case "MobileIpAddress":
query = SortDirection == SortDirection.Ascending
- ? query.OrderBy(x => x.MobileIpAddress)
- : query.OrderByDescending(x => x.MobileIpAddress);
+ ? query.OrderBy(x => x.Request.MobileIpAddress)
+ : query.OrderByDescending(x => x.Request.MobileIpAddress);
break;
case "FulfilledAt":
query = SortDirection == SortDirection.Ascending
- ? query.OrderBy(x => x.FulfilledAt)
- : query.OrderByDescending(x => x.FulfilledAt);
+ ? query.OrderBy(x => x.Request.FulfilledAt)
+ : query.OrderByDescending(x => x.Request.FulfilledAt);
break;
case "RetrievedAt":
query = SortDirection == SortDirection.Ascending
- ? query.OrderBy(x => x.RetrievedAt)
- : query.OrderByDescending(x => x.RetrievedAt);
+ ? query.OrderBy(x => x.Request.RetrievedAt)
+ : query.OrderByDescending(x => x.Request.RetrievedAt);
break;
case "Username":
query = SortDirection == SortDirection.Ascending
@@ -342,7 +352,7 @@ else
: query.OrderByDescending(x => x.Username);
break;
default:
- query = query.OrderByDescending(x => x.CreatedAt);
+ query = query.OrderByDescending(x => x.Request.CreatedAt);
break;
}
diff --git a/apps/server/AliasVault.Api/Controllers/AuthController.cs b/apps/server/AliasVault.Api/Controllers/AuthController.cs
index 4facc5ad7..bec017f33 100644
--- a/apps/server/AliasVault.Api/Controllers/AuthController.cs
+++ b/apps/server/AliasVault.Api/Controllers/AuthController.cs
@@ -15,6 +15,7 @@ using AliasServerDb;
using AliasVault.Api.Helpers;
using AliasVault.Auth;
using AliasVault.Cryptography.Client;
+using AliasVault.Cryptography.Server;
using AliasVault.Shared.Core;
using AliasVault.Shared.Models.Enums;
using AliasVault.Shared.Models.WebApi;
@@ -590,14 +591,20 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
// If not fulfilled, return pending status
if (loginRequest.FulfilledAt == null)
{
- return Ok(new MobileLoginPollResponse(false, null, null, null, null, null, null));
+ return Ok(new MobileLoginPollResponse(false, null, null, null, null, null));
}
- // Sanity check: check if user exists.
- var user = await userManager.FindByNameAsync(loginRequest.Username!);
+ // Check if already retrieved (one-time use protection)
+ if (loginRequest.RetrievedAt != null)
+ {
+ return NotFound(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.MOBILE_LOGIN_REQUEST_NOT_FOUND, 404));
+ }
+
+ // Sanity check: check if user exists using UserId FK
+ var user = await userManager.FindByIdAsync(loginRequest.UserId!);
if (user == null)
{
- await authLoggingService.LogAuthEventFailAsync(loginRequest.Username!, AuthEventType.MobileLogin, AuthFailureReason.InvalidUsername);
+ await authLoggingService.LogAuthEventFailAsync("n/a", AuthEventType.MobileLogin, AuthFailureReason.InvalidUsername);
return BadRequest(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USER_NOT_FOUND, 400));
}
@@ -618,29 +625,36 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
// Generate token for the user
var tokenModel = await GenerateNewTokensForUser(user, extendedLifetime: true);
+ // Generate a single symmetric key for encrypting all fields
+ var symmetricKey = Cryptography.Server.Encryption.GenerateRandomSymmetricKey();
+
+ // Encrypt each field with the symmetric key (returns base64)
+ var encryptedToken = Cryptography.Server.Encryption.SymmetricEncrypt(tokenModel.Token, symmetricKey);
+ var encryptedRefreshToken = Cryptography.Server.Encryption.SymmetricEncrypt(tokenModel.RefreshToken, symmetricKey);
+ var encryptedDecryptionKey = Cryptography.Server.Encryption.SymmetricEncrypt(loginRequest.EncryptedDecryptionKey!, symmetricKey);
+ var encryptedUsername = Cryptography.Server.Encryption.SymmetricEncrypt(user.UserName!, symmetricKey);
+
+ // Encrypt the symmetric key with the client's RSA public key (returns base64)
+ var encryptedSymmetricKey = Cryptography.Server.Encryption.EncryptSymmetricKeyWithRsa(symmetricKey, loginRequest.ClientPublicKey);
+
// Log successful mobile login authentication
await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.MobileLogin);
- // Return fulfilled response with encrypted key and token
- var response = new MobileLoginPollResponse(
- true,
- loginRequest.EncryptedDecryptionKey,
- loginRequest.Username,
- tokenModel,
- loginRequest.Salt,
- loginRequest.EncryptionType,
- loginRequest.EncryptionSettings);
-
- // Clear sensitive data but keep the record for statistics
+ // Mark as retrieved and clear sensitive data
loginRequest.ClientPublicKey = string.Empty;
loginRequest.EncryptedDecryptionKey = null;
- loginRequest.Salt = null;
- loginRequest.EncryptionType = null;
- loginRequest.EncryptionSettings = null;
loginRequest.RetrievedAt = timeProvider.UtcNow;
await context.SaveChangesAsync();
- return Ok(response);
+ // Return response with encrypted symmetric key and encrypted fields
+ // Client will decrypt username to call /login endpoint for salt and encryption settings
+ return Ok(new MobileLoginPollResponse(
+ true,
+ encryptedSymmetricKey,
+ encryptedToken,
+ encryptedRefreshToken,
+ encryptedDecryptionKey,
+ encryptedUsername));
}
///
@@ -684,12 +698,6 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USER_NOT_FOUND, 401));
}
- // Verify the username matches the authenticated user
- if (user.UserName != model.Username)
- {
- return BadRequest(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USERNAME_MISMATCH, 400));
- }
-
var loginRequest = await context.MobileLoginRequests.FirstOrDefaultAsync(r => r.Id == model.RequestId);
// Check if request exists and hasn't expired
@@ -704,15 +712,8 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
return BadRequest(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.MOBILE_LOGIN_REQUEST_ALREADY_FULFILLED, 400));
}
- // Get latest vault encryption settings for the user
- var latestVaultEncryptionSettings = AuthHelper.GetUserLatestVaultEncryptionSettings(user);
-
- // Update the login request with the encrypted key and user info
+ // Update the login request with the encrypted key and user ID
loginRequest.EncryptedDecryptionKey = model.EncryptedDecryptionKey;
- loginRequest.Username = model.Username;
- loginRequest.Salt = latestVaultEncryptionSettings.Salt;
- loginRequest.EncryptionType = latestVaultEncryptionSettings.EncryptionType;
- loginRequest.EncryptionSettings = latestVaultEncryptionSettings.EncryptionSettings;
loginRequest.UserId = user.Id;
loginRequest.FulfilledAt = timeProvider.UtcNow;
loginRequest.MobileIpAddress = IpAddressUtility.GetIpFromContext(HttpContext);
diff --git a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
index 54c873584..ee93dfdc7 100644
--- a/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
+++ b/apps/server/AliasVault.Client/wwwroot/css/tailwind.css
@@ -1825,6 +1825,11 @@ video {
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
}
+.bg-yellow-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 249 195 / var(--tw-bg-opacity));
+}
+
.bg-opacity-50 {
--tw-bg-opacity: 0.5;
}
diff --git a/apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.Designer.cs b/apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.Designer.cs
similarity index 98%
rename from apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.Designer.cs
rename to apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.Designer.cs
index 28263febe..bc65a99fe 100644
--- a/apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.Designer.cs
+++ b/apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.Designer.cs
@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace AliasServerDb.Migrations
{
[DbContext(typeof(AliasServerDbContext))]
- [Migration("20251117203100_AddMobileLoginRequest")]
+ [Migration("20251117215634_AddMobileLoginRequest")]
partial class AddMobileLoginRequest
{
///
@@ -512,12 +512,6 @@ namespace AliasServerDb.Migrations
b.Property("EncryptedDecryptionKey")
.HasColumnType("text");
- b.Property("EncryptionSettings")
- .HasColumnType("text");
-
- b.Property("EncryptionType")
- .HasColumnType("text");
-
b.Property("FulfilledAt")
.HasColumnType("timestamp with time zone");
@@ -527,15 +521,9 @@ namespace AliasServerDb.Migrations
b.Property("RetrievedAt")
.HasColumnType("timestamp with time zone");
- b.Property("Salt")
- .HasColumnType("text");
-
b.Property("UserId")
.HasColumnType("text");
- b.Property("Username")
- .HasColumnType("text");
-
b.HasKey("Id");
b.HasIndex(new[] { "ClientIpAddress" }, "IX_ClientIpAddress");
diff --git a/apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.cs b/apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.cs
similarity index 88%
rename from apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.cs
rename to apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.cs
index 6aa4b1194..ca1388dd1 100644
--- a/apps/server/Databases/AliasServerDb/Migrations/20251117203100_AddMobileLoginRequest.cs
+++ b/apps/server/Databases/AliasServerDb/Migrations/20251117215634_AddMobileLoginRequest.cs
@@ -1,5 +1,4 @@
-//
-using System;
+using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
@@ -19,10 +18,6 @@ namespace AliasServerDb.Migrations
Id = table.Column(type: "text", nullable: false),
ClientPublicKey = table.Column(type: "text", nullable: false),
EncryptedDecryptionKey = table.Column(type: "text", nullable: true),
- Username = table.Column(type: "text", nullable: true),
- Salt = table.Column(type: "text", nullable: true),
- EncryptionType = table.Column(type: "text", nullable: true),
- EncryptionSettings = table.Column(type: "text", nullable: true),
CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
FulfilledAt = table.Column(type: "timestamp with time zone", nullable: true),
RetrievedAt = table.Column(type: "timestamp with time zone", nullable: true),
diff --git a/apps/server/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs b/apps/server/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs
index c598cb0c9..4d5957ab1 100644
--- a/apps/server/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs
+++ b/apps/server/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs
@@ -509,12 +509,6 @@ namespace AliasServerDb.Migrations
b.Property("EncryptedDecryptionKey")
.HasColumnType("text");
- b.Property("EncryptionSettings")
- .HasColumnType("text");
-
- b.Property("EncryptionType")
- .HasColumnType("text");
-
b.Property("FulfilledAt")
.HasColumnType("timestamp with time zone");
@@ -524,15 +518,9 @@ namespace AliasServerDb.Migrations
b.Property("RetrievedAt")
.HasColumnType("timestamp with time zone");
- b.Property("Salt")
- .HasColumnType("text");
-
b.Property("UserId")
.HasColumnType("text");
- b.Property("Username")
- .HasColumnType("text");
-
b.HasKey("Id");
b.HasIndex(new[] { "ClientIpAddress" }, "IX_ClientIpAddress");
diff --git a/apps/server/Databases/AliasServerDb/MobileLoginRequest.cs b/apps/server/Databases/AliasServerDb/MobileLoginRequest.cs
index af759eac0..dfe34dbac 100644
--- a/apps/server/Databases/AliasServerDb/MobileLoginRequest.cs
+++ b/apps/server/Databases/AliasServerDb/MobileLoginRequest.cs
@@ -35,30 +35,6 @@ public class MobileLoginRequest
///
public string? EncryptedDecryptionKey { get; set; }
- ///
- /// Gets or sets the username provided by the mobile app.
- /// Will be null until mobile app responds.
- ///
- public string? Username { get; set; }
-
- ///
- /// Gets or sets the salt for the user.
- /// Will be populated when mobile app provides the username.
- ///
- public string? Salt { get; set; }
-
- ///
- /// Gets or sets the encryption type for the user.
- /// Will be populated when mobile app provides the username.
- ///
- public string? EncryptionType { get; set; }
-
- ///
- /// Gets or sets the encryption settings for the user.
- /// Will be populated when mobile app provides the username.
- ///
- public string? EncryptionSettings { get; set; }
-
///
/// Gets or sets the created timestamp.
///
diff --git a/apps/server/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs b/apps/server/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs
index 5c8484663..8f58140bc 100644
--- a/apps/server/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs
+++ b/apps/server/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs
@@ -99,9 +99,6 @@ public class LogCleanupTask : IMaintenanceTask
// Clear all sensitive data
request.ClientPublicKey = string.Empty;
request.EncryptedDecryptionKey = null;
- request.Salt = null;
- request.EncryptionType = null;
- request.EncryptionSettings = null;
request.ClearedAt = now;
}
diff --git a/apps/server/Shared/AliasVault.RazorComponents/StatusPill.razor b/apps/server/Shared/AliasVault.RazorComponents/StatusPill.razor
index 31a82f827..e7f953f48 100644
--- a/apps/server/Shared/AliasVault.RazorComponents/StatusPill.razor
+++ b/apps/server/Shared/AliasVault.RazorComponents/StatusPill.razor
@@ -23,9 +23,32 @@
[Parameter]
public string TextFalse { get; set; } = "Disabled";
- private string PillClass => Enabled
- ? "bg-green-100 text-green-800"
- : "bg-red-100 text-red-800";
+ ///
+ /// Optional color override: "green", "yellow", "red". If not specified, uses Enabled parameter.
+ ///
+ [Parameter]
+ public string? Color { get; set; }
+
+ private string PillClass
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Color))
+ {
+ return Color.ToLower() switch
+ {
+ "green" => "bg-green-100 text-green-800",
+ "yellow" => "bg-yellow-100 text-yellow-800",
+ "red" => "bg-red-100 text-red-800",
+ _ => "bg-gray-100 text-gray-800"
+ };
+ }
+
+ return Enabled
+ ? "bg-green-100 text-green-800"
+ : "bg-red-100 text-red-800";
+ }
+ }
private string StatusText => Enabled ? TextTrue : TextFalse;
}
diff --git a/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginPollResponse.cs b/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginPollResponse.cs
index d60d547d9..f8d6ca80b 100644
--- a/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginPollResponse.cs
+++ b/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginPollResponse.cs
@@ -9,19 +9,19 @@ namespace AliasVault.Shared.Models.WebApi.Auth;
///
/// Response model for polling mobile login status.
+/// All sensitive data is encrypted with AES-256, and the AES key is encrypted with client's RSA public key.
+/// Client decrypts username to call /login endpoint for salt and encryption settings.
///
/// Whether the request has been fulfilled by the mobile app.
-/// The encrypted decryption key (base64 encoded) if fulfilled.
-/// The username of the user logging in if fulfilled.
-/// The authentication token if fulfilled.
-/// The salt for key derivation if fulfilled.
-/// The encryption type if fulfilled.
-/// The encryption settings if fulfilled.
+/// The AES symmetric key encrypted with client's RSA public key (base64 encoded). Used to decrypt all encrypted fields. Null if not fulfilled.
+/// The JWT token encrypted with AES symmetric key (base64 encoded). Null if not fulfilled.
+/// The refresh token encrypted with AES symmetric key (base64 encoded). Null if not fulfilled.
+/// The vault decryption key encrypted with AES symmetric key (base64 encoded). Null if not fulfilled.
+/// The username encrypted with AES symmetric key (base64 encoded). Retrieved from User via UserId FK. Null if not fulfilled.
public record MobileLoginPollResponse(
bool Fulfilled,
+ string? EncryptedSymmetricKey,
+ string? EncryptedToken,
+ string? EncryptedRefreshToken,
string? EncryptedDecryptionKey,
- string? Username,
- TokenModel? Token,
- string? Salt,
- string? EncryptionType,
- string? EncryptionSettings);
+ string? EncryptedUsername);
diff --git a/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginSubmitRequest.cs b/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginSubmitRequest.cs
index a874ab462..c0e3faf39 100644
--- a/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginSubmitRequest.cs
+++ b/apps/server/Shared/AliasVault.Shared/Models/WebApi/Auth/MobileLoginSubmitRequest.cs
@@ -12,5 +12,4 @@ namespace AliasVault.Shared.Models.WebApi.Auth;
///
/// The unique identifier for this login request.
/// The encrypted decryption key (base64 encoded).
-/// The username of the user logging in.
-public record MobileLoginSubmitRequest(string RequestId, string EncryptedDecryptionKey, string Username);
+public record MobileLoginSubmitRequest(string RequestId, string EncryptedDecryptionKey);
diff --git a/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs b/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
index f0c08ada1..3e7c82818 100644
--- a/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
+++ b/apps/server/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs
@@ -546,10 +546,7 @@ public class TaskRunnerTests
Id = Guid.NewGuid().ToString(),
ClientPublicKey = "stale-public-key",
EncryptedDecryptionKey = "encrypted-key-data",
- Username = "testuser",
- Salt = "test-salt",
- EncryptionType = "Argon2id",
- EncryptionSettings = "{\"iterations\":3}",
+ UserId = "user-id-1",
CreatedAt = DateTime.UtcNow.AddMinutes(-15),
FulfilledAt = DateTime.UtcNow.AddMinutes(-12), // Fulfilled 12 minutes ago (exceeds 10 min timeout)
RetrievedAt = null, // Not yet retrieved
@@ -565,10 +562,7 @@ public class TaskRunnerTests
Id = Guid.NewGuid().ToString(),
ClientPublicKey = "recent-public-key",
EncryptedDecryptionKey = "encrypted-key-data",
- Username = "testuser2",
- Salt = "test-salt",
- EncryptionType = "Argon2id",
- EncryptionSettings = "{\"iterations\":3}",
+ UserId = "user-id-2",
CreatedAt = DateTime.UtcNow.AddMinutes(-6),
FulfilledAt = DateTime.UtcNow.AddMinutes(-5), // Fulfilled 5 minutes ago (under 10 min timeout)
RetrievedAt = null,
@@ -584,7 +578,7 @@ public class TaskRunnerTests
Id = Guid.NewGuid().ToString(),
ClientPublicKey = "completed-public-key",
EncryptedDecryptionKey = "encrypted-key-data",
- Username = "testuser3",
+ UserId = "user-id-3",
CreatedAt = DateTime.UtcNow.AddMinutes(-15),
FulfilledAt = DateTime.UtcNow.AddMinutes(-12),
RetrievedAt = DateTime.UtcNow.AddMinutes(-11), // Already retrieved
@@ -611,15 +605,12 @@ public class TaskRunnerTests
// Stale request should have sensitive data cleared
Assert.That(staleAfterCleanup.ClientPublicKey, Is.Empty, "Stale request ClientPublicKey should be cleared");
Assert.That(staleAfterCleanup.EncryptedDecryptionKey, Is.Null, "Stale request EncryptedDecryptionKey should be cleared");
- Assert.That(staleAfterCleanup.Salt, Is.Null, "Stale request Salt should be cleared");
- Assert.That(staleAfterCleanup.EncryptionType, Is.Null, "Stale request EncryptionType should be cleared");
- Assert.That(staleAfterCleanup.EncryptionSettings, Is.Null, "Stale request EncryptionSettings should be cleared");
Assert.That(staleAfterCleanup.ClearedAt, Is.Not.Null, "Stale request ClearedAt should be set");
// Metadata should be preserved for abuse tracking
Assert.That(staleAfterCleanup.ClientIpAddress, Is.EqualTo("192.168.1.1"), "Client IP should be preserved");
Assert.That(staleAfterCleanup.MobileIpAddress, Is.EqualTo("10.0.0.1"), "Mobile IP should be preserved");
- Assert.That(staleAfterCleanup.Username, Is.EqualTo("testuser"), "Username should be preserved");
+ Assert.That(staleAfterCleanup.UserId, Is.EqualTo("user-id-1"), "UserId should be preserved");
});
var recentAfterCleanup = recentRequest;