mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-19 13:57:18 -04:00
Add fixed SrpIdentity column to allow username changes (#1429)
This commit is contained in:
@@ -13,6 +13,8 @@ export type RegisterRequest = {
|
||||
verifier: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
/** The SRP identity used for authentication (a random GUID generated at registration). */
|
||||
srpIdentity: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,12 +195,22 @@ export class SrpAuthService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random UUID v4 for use as SRP identity.
|
||||
*
|
||||
* @returns A random UUID string
|
||||
*/
|
||||
public static generateSrpIdentity(): string {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares SRP registration data for a new user.
|
||||
*
|
||||
* This generates all the cryptographic values needed to register a user:
|
||||
* - Salt for key derivation
|
||||
* - Verifier for SRP authentication
|
||||
* - SRP identity (random GUID) for immutable authentication identity
|
||||
*
|
||||
* @param username - The username for registration
|
||||
* @param password - The password for registration
|
||||
@@ -211,6 +223,12 @@ export class SrpAuthService {
|
||||
const normalizedUsername = SrpAuthService.normalizeUsername(username);
|
||||
const salt = SrpAuthService.generateSalt();
|
||||
|
||||
/**
|
||||
* Generate a random GUID for SRP identity. This is used for all SRP operations,
|
||||
* is set during registration, and never changes.
|
||||
*/
|
||||
const srpIdentity = SrpAuthService.generateSrpIdentity();
|
||||
|
||||
// Derive key from password using default Argon2Id settings
|
||||
const credentials = await SrpAuthService.prepareCredentials(
|
||||
password,
|
||||
@@ -219,8 +237,8 @@ export class SrpAuthService {
|
||||
DEFAULT_ENCRYPTION.settings
|
||||
);
|
||||
|
||||
// Generate SRP private key and verifier
|
||||
const privateKey = SrpAuthService.derivePrivateKey(salt, normalizedUsername, credentials.passwordHashString);
|
||||
// Generate SRP private key and verifier using srpIdentity (not username)
|
||||
const privateKey = SrpAuthService.derivePrivateKey(salt, srpIdentity, credentials.passwordHashString);
|
||||
const verifier = SrpAuthService.deriveVerifier(privateKey);
|
||||
|
||||
return {
|
||||
@@ -229,6 +247,7 @@ export class SrpAuthService {
|
||||
verifier,
|
||||
encryptionType: DEFAULT_ENCRYPTION.type,
|
||||
encryptionSettings: DEFAULT_ENCRYPTION.settings,
|
||||
srpIdentity,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -328,6 +347,12 @@ export class SrpAuthService {
|
||||
|
||||
const loginResponse = (await initiateResponse.json()) as LoginResponse;
|
||||
|
||||
/**
|
||||
* Use srpIdentity from server response if available, otherwise fall back to normalized username.
|
||||
* Note: the fallback can be removed in the future after 0.26.0+ is deployed.
|
||||
*/
|
||||
const srpIdentity = loginResponse.srpIdentity ?? normalizedUsername;
|
||||
|
||||
// Step 2: Prepare credentials
|
||||
const credentials = await SrpAuthService.prepareCredentials(
|
||||
password,
|
||||
@@ -336,18 +361,18 @@ export class SrpAuthService {
|
||||
loginResponse.encryptionSettings
|
||||
);
|
||||
|
||||
// Step 3: Generate SRP session
|
||||
// Step 3: Generate SRP session using srpIdentity (not the typed username)
|
||||
const clientEphemeral = SrpAuthService.generateEphemeral();
|
||||
const privateKey = SrpAuthService.derivePrivateKey(
|
||||
loginResponse.salt,
|
||||
normalizedUsername,
|
||||
srpIdentity,
|
||||
credentials.passwordHashString
|
||||
);
|
||||
const session = SrpAuthService.deriveSession(
|
||||
clientEphemeral.secret,
|
||||
loginResponse.serverEphemeral,
|
||||
loginResponse.salt,
|
||||
normalizedUsername,
|
||||
srpIdentity,
|
||||
privateKey
|
||||
);
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ type LoginResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -318,6 +319,7 @@ type PasswordChangeInitiateResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,18 +49,19 @@ export class SrpUtility {
|
||||
rememberMe: boolean,
|
||||
loginResponse: LoginResponse
|
||||
): Promise<ValidateLoginResponse> {
|
||||
// Generate client ephemeral
|
||||
/**
|
||||
* Use srpIdentity from server response if available, otherwise fall back to username.
|
||||
* Note: the fallback can be removed in the future after 0.26.0+ is deployed.
|
||||
*/
|
||||
const srpIdentity = loginResponse.srpIdentity ?? username;
|
||||
|
||||
const clientEphemeral = srp.generateEphemeral();
|
||||
|
||||
// Derive private key
|
||||
const privateKey = srp.derivePrivateKey(loginResponse.salt, username, passwordHash);
|
||||
|
||||
// Derive session
|
||||
const privateKey = srp.derivePrivateKey(loginResponse.salt, srpIdentity, passwordHash);
|
||||
const sessionProof = srp.deriveSession(
|
||||
clientEphemeral.secret,
|
||||
loginResponse.serverEphemeral,
|
||||
loginResponse.salt,
|
||||
username,
|
||||
srpIdentity,
|
||||
privateKey
|
||||
);
|
||||
|
||||
@@ -98,18 +99,19 @@ export class SrpUtility {
|
||||
loginResponse: LoginResponse,
|
||||
twoFactorCode: number
|
||||
): Promise<ValidateLoginResponse> {
|
||||
// Generate client ephemeral
|
||||
/**
|
||||
* Use srpIdentity from server response if available, otherwise fall back to username.
|
||||
* Note: the fallback can be removed in the future after 0.26.0+ is deployed.
|
||||
*/
|
||||
const srpIdentity = loginResponse.srpIdentity ?? username;
|
||||
|
||||
const clientEphemeral = srp.generateEphemeral();
|
||||
|
||||
// Derive private key
|
||||
const privateKey = srp.derivePrivateKey(loginResponse.salt, username, passwordHash);
|
||||
|
||||
// Derive session
|
||||
const privateKey = srp.derivePrivateKey(loginResponse.salt, srpIdentity, passwordHash);
|
||||
const sessionProof = srp.deriveSession(
|
||||
clientEphemeral.secret,
|
||||
loginResponse.serverEphemeral,
|
||||
loginResponse.salt,
|
||||
username,
|
||||
srpIdentity,
|
||||
privateKey
|
||||
);
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ type LoginResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -318,6 +319,7 @@ type PasswordChangeInitiateResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -166,13 +166,17 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
// Retrieve latest vault of user which contains the current salt and verifier.
|
||||
var latestVaultEncryptionSettings = AuthHelper.GetUserLatestVaultEncryptionSettings(user);
|
||||
|
||||
// Get or create SRP identity. For existing users without SrpIdentity, fall back to username (lowercase).
|
||||
var srpIdentity = user.SrpIdentity ?? user.UserName!.ToLowerInvariant();
|
||||
|
||||
// Server creates ephemeral and sends to client
|
||||
var ephemeral = Srp.GenerateEphemeralServer(latestVaultEncryptionSettings.Verifier);
|
||||
|
||||
// Store the server ephemeral in memory cache for Validate() endpoint to use.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + model.Username, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
// Use SrpIdentity as the cache key to ensure consistency.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + srpIdentity, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
|
||||
return Ok(new LoginInitiateResponse(latestVaultEncryptionSettings.Salt, ephemeral.Public, latestVaultEncryptionSettings.EncryptionType, latestVaultEncryptionSettings.EncryptionSettings));
|
||||
return Ok(new LoginInitiateResponse(latestVaultEncryptionSettings.Salt, ephemeral.Public, latestVaultEncryptionSettings.EncryptionType, latestVaultEncryptionSettings.EncryptionSettings, srpIdentity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -416,9 +420,14 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
return BadRequest(ApiErrorCodeHelper.CreateValidationErrorResponse(apiErrorCode, 400));
|
||||
}
|
||||
|
||||
// Use the SrpIdentity from the request if provided (typically a GUID generated by the client),
|
||||
// otherwise fall back to lowercase username for backward compatibility.
|
||||
var srpIdentity = model.SrpIdentity ?? model.Username.ToLowerInvariant();
|
||||
|
||||
var user = new AliasVaultUser
|
||||
{
|
||||
UserName = model.Username,
|
||||
SrpIdentity = srpIdentity,
|
||||
CreatedAt = timeProvider.UtcNow,
|
||||
UpdatedAt = timeProvider.UtcNow,
|
||||
PasswordChangedAt = timeProvider.UtcNow,
|
||||
@@ -476,13 +485,17 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
// Retrieve latest vault of user which contains the current salt and verifier.
|
||||
var latestVaultEncryptionSettings = AuthHelper.GetUserLatestVaultEncryptionSettings(user);
|
||||
|
||||
// Get or create SRP identity. For existing users without SrpIdentity, fall back to username (lowercase).
|
||||
var srpIdentity = user.SrpIdentity ?? user.UserName!.ToLowerInvariant();
|
||||
|
||||
// Server creates ephemeral and sends to client
|
||||
var ephemeral = Srp.GenerateEphemeralServer(latestVaultEncryptionSettings.Verifier);
|
||||
|
||||
// Store the server ephemeral in memory cache for the Vault update (and set new password) endpoint to use.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + user.UserName!, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
// Use SrpIdentity as the cache key to ensure consistency.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + srpIdentity, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
|
||||
return Ok(new PasswordChangeInitiateResponse(latestVaultEncryptionSettings.Salt, ephemeral.Public, latestVaultEncryptionSettings.EncryptionType, latestVaultEncryptionSettings.EncryptionSettings));
|
||||
return Ok(new PasswordChangeInitiateResponse(latestVaultEncryptionSettings.Salt, ephemeral.Public, latestVaultEncryptionSettings.EncryptionType, latestVaultEncryptionSettings.EncryptionSettings, srpIdentity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -542,17 +555,22 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
// Retrieve latest vault of user which contains the current salt and verifier.
|
||||
var latestVaultEncryptionSettings = AuthHelper.GetUserLatestVaultEncryptionSettings(user);
|
||||
|
||||
// Get or create SRP identity. For existing users without SrpIdentity, fall back to username (lowercase).
|
||||
var srpIdentity = user.SrpIdentity ?? user.UserName!.ToLowerInvariant();
|
||||
|
||||
// Server creates ephemeral and sends to client
|
||||
var ephemeral = Srp.GenerateEphemeralServer(latestVaultEncryptionSettings.Verifier);
|
||||
|
||||
// Store the server ephemeral in memory cache for confirmation endpoint.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + model.Username, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
// Use SrpIdentity as the cache key to ensure consistency.
|
||||
cache.Set(AuthHelper.CachePrefixEphemeral + srpIdentity, ephemeral.Secret, TimeSpan.FromMinutes(5));
|
||||
|
||||
return Ok(new LoginInitiateResponse(
|
||||
latestVaultEncryptionSettings.Salt,
|
||||
ephemeral.Public,
|
||||
latestVaultEncryptionSettings.EncryptionType,
|
||||
latestVaultEncryptionSettings.EncryptionSettings));
|
||||
latestVaultEncryptionSettings.EncryptionSettings,
|
||||
srpIdentity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,16 +28,19 @@ public static class AuthHelper
|
||||
public static readonly string CachePrefixFakeData = "FakeData_";
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that validates the SRP session based on provided username, ephemeral and proof.
|
||||
/// Helper method that validates the SRP session based on provided SRP identity, ephemeral and proof.
|
||||
/// </summary>
|
||||
/// <param name="cache">IMemoryCache instance.</param>
|
||||
/// <param name="user">The user object.</param>
|
||||
/// <param name="clientEphemeral">The client ephemeral value.</param>
|
||||
/// <param name="clientSessionProof">The client session proof.</param>
|
||||
/// <returns>Tuple.</returns>
|
||||
/// <returns>SrpSession if validation succeeds, null otherwise.</returns>
|
||||
public static SrpSession? ValidateSrpSession(IMemoryCache cache, AliasVaultUser user, string clientEphemeral, string clientSessionProof)
|
||||
{
|
||||
if (!cache.TryGetValue(CachePrefixEphemeral + user.UserName, out var serverSecretEphemeral) || serverSecretEphemeral is not string)
|
||||
// Get or create SRP identity. For existing users without SrpIdentity, fall back to username (lowercase).
|
||||
var srpIdentity = user.SrpIdentity ?? user.UserName!.ToLowerInvariant();
|
||||
|
||||
if (!cache.TryGetValue(CachePrefixEphemeral + srpIdentity, out var serverSecretEphemeral) || serverSecretEphemeral is not string)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -45,11 +48,13 @@ public static class AuthHelper
|
||||
// Retrieve latest vault of user which contains the current salt and verifier.
|
||||
var latestVaultEncryptionSettings = GetUserLatestVaultEncryptionSettings(user);
|
||||
|
||||
// Use SrpIdentity for the SRP session derivation. This is the fixed identity that was used
|
||||
// when the verifier was originally created, ensuring username changes don't break authentication.
|
||||
var serverSession = Srp.DeriveSessionServer(
|
||||
serverSecretEphemeral.ToString() ?? string.Empty,
|
||||
clientEphemeral,
|
||||
latestVaultEncryptionSettings.Salt,
|
||||
user.UserName ?? string.Empty,
|
||||
srpIdentity,
|
||||
latestVaultEncryptionSettings.Verifier,
|
||||
clientSessionProof);
|
||||
|
||||
|
||||
@@ -263,18 +263,22 @@ else
|
||||
];
|
||||
}
|
||||
|
||||
// Use srpIdentity from server response if available, otherwise fall back to username.
|
||||
// Note: the fallback can be removed in the future after 0.26.0+ is deployed.
|
||||
var srpIdentity = loginResponse.SrpIdentity ?? username;
|
||||
|
||||
// 3. Client derives shared session key.
|
||||
_passwordHash = await Encryption.DeriveKeyFromPasswordAsync(_loginModel.Password, loginResponse.Salt, loginResponse.EncryptionType, loginResponse.EncryptionSettings);
|
||||
var passwordHashString = BitConverter.ToString(_passwordHash).Replace("-", string.Empty);
|
||||
|
||||
_clientEphemeral = Srp.GenerateEphemeralClient();
|
||||
var privateKey = Srp.DerivePrivateKey(loginResponse.Salt, username, passwordHashString);
|
||||
var privateKey = Srp.DerivePrivateKey(loginResponse.Salt, srpIdentity, passwordHashString);
|
||||
_clientSession = Srp.DeriveSessionClient(
|
||||
privateKey,
|
||||
_clientEphemeral.Secret,
|
||||
loginResponse.ServerEphemeral,
|
||||
loginResponse.Salt,
|
||||
username);
|
||||
srpIdentity);
|
||||
|
||||
// 4. Client sends proof of session key to server.
|
||||
result = await Http.PostAsJsonAsync("v1/Auth/validate", new ValidateLoginRequest(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof));
|
||||
|
||||
@@ -93,6 +93,12 @@ else
|
||||
/// </summary>
|
||||
private string CurrentEncryptionSettings { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SRP identity for authentication. This is a fixed value that doesn't change
|
||||
/// even if the display username is updated.
|
||||
/// </summary>
|
||||
private string? CurrentSrpIdentity { get; set; }
|
||||
|
||||
private SrpEphemeral ClientEphemeral = new();
|
||||
private SrpSession ClientSession = new();
|
||||
|
||||
@@ -141,6 +147,7 @@ else
|
||||
CurrentSalt = response.Salt;
|
||||
CurrentEncryptionType = response.EncryptionType;
|
||||
CurrentEncryptionSettings = response.EncryptionSettings;
|
||||
CurrentSrpIdentity = response.SrpIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -158,13 +165,18 @@ else
|
||||
|
||||
ClientEphemeral = Srp.GenerateEphemeralClient();
|
||||
var username = await GetUsernameAsync();
|
||||
var privateKey = Srp.DerivePrivateKey(CurrentSalt, username, currentPasswordHashString);
|
||||
|
||||
// Use srpIdentity from server response if available, otherwise fall back to username.
|
||||
// Note: the fallback can be removed in the future after 0.26.0+ is deployed.
|
||||
var srpIdentity = CurrentSrpIdentity ?? username.ToLowerInvariant();
|
||||
|
||||
var privateKey = Srp.DerivePrivateKey(CurrentSalt, srpIdentity, currentPasswordHashString);
|
||||
ClientSession = Srp.DeriveSessionClient(
|
||||
privateKey,
|
||||
ClientEphemeral.Secret,
|
||||
CurrentServerEphemeral,
|
||||
CurrentSalt,
|
||||
username);
|
||||
srpIdentity);
|
||||
|
||||
// Generate salt and verifier for new password.
|
||||
var client = new SrpClient();
|
||||
@@ -180,7 +192,8 @@ else
|
||||
// it is encrypted with the new password hash.
|
||||
await AuthService.StoreEncryptionKeyAsync(newPasswordHash);
|
||||
|
||||
var srpPasswordChange = Srp.PasswordChangeAsync(client, newSalt, username, newPasswordHashString);
|
||||
// Use srpIdentity for generating the new verifier to maintain consistency.
|
||||
var srpPasswordChange = Srp.PasswordChangeAsync(client, newSalt, srpIdentity, newPasswordHashString);
|
||||
|
||||
// Prepare new vault model to update to.
|
||||
var encryptedBase64String = await DbService.GetEncryptedDatabaseBase64String();
|
||||
|
||||
@@ -171,18 +171,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Use srpIdentity from server response if available, otherwise fall back to username.
|
||||
// Note: the fallback can be removed in the future after 0.27.0+ is deployed.
|
||||
var srpIdentity = loginResponse.SrpIdentity ?? username;
|
||||
|
||||
// Verify password using SRP
|
||||
var passwordHash = await Encryption.DeriveKeyFromPasswordAsync(_passwordModel.Password, loginResponse.Salt, loginResponse.EncryptionType, loginResponse.EncryptionSettings);
|
||||
var passwordHashString = BitConverter.ToString(passwordHash).Replace("-", string.Empty);
|
||||
|
||||
ClientEphemeral = Srp.GenerateEphemeralClient();
|
||||
var privateKey = Srp.DerivePrivateKey(loginResponse.Salt, username, passwordHashString);
|
||||
var privateKey = Srp.DerivePrivateKey(loginResponse.Salt, srpIdentity, passwordHashString);
|
||||
ClientSession = Srp.DeriveSessionClient(
|
||||
privateKey,
|
||||
ClientEphemeral.Secret,
|
||||
loginResponse.ServerEphemeral,
|
||||
loginResponse.Salt,
|
||||
username);
|
||||
srpIdentity);
|
||||
|
||||
// Send final delete request with SRP proof.
|
||||
result = await Http.PostAsJsonAsync("v1/Auth/delete-account/confirm", new DeleteAccountRequest(username, ClientEphemeral.Public, ClientSession.Proof));
|
||||
|
||||
@@ -41,6 +41,10 @@ public class UserRegistrationService(HttpClient httpClient, AuthenticationStateP
|
||||
var client = new SrpClient();
|
||||
var salt = client.GenerateSalt();
|
||||
|
||||
// Generate a random GUID for SRP identity. This is used for all SRP operations,
|
||||
// is set during registration, and never changes.
|
||||
var srpIdentity = Guid.NewGuid().ToString();
|
||||
|
||||
string encryptionType = Defaults.EncryptionType;
|
||||
string encryptionSettings = Defaults.EncryptionSettings;
|
||||
if (config.CryptographyOverrideType is not null && config.CryptographyOverrideSettings is not null)
|
||||
@@ -51,9 +55,9 @@ public class UserRegistrationService(HttpClient httpClient, AuthenticationStateP
|
||||
|
||||
var passwordHash = await Encryption.DeriveKeyFromPasswordAsync(password, salt, encryptionType, encryptionSettings);
|
||||
var passwordHashString = BitConverter.ToString(passwordHash).Replace("-", string.Empty);
|
||||
var srpSignup = Srp.PasswordChangeAsync(client, salt, username, passwordHashString);
|
||||
var srpSignup = Srp.PasswordChangeAsync(client, salt, srpIdentity, passwordHashString);
|
||||
|
||||
var registerRequest = new RegisterRequest(srpSignup.Username, srpSignup.Salt, srpSignup.Verifier, encryptionType, encryptionSettings);
|
||||
var registerRequest = new RegisterRequest(username, srpSignup.Salt, srpSignup.Verifier, encryptionType, encryptionSettings, srpIdentity);
|
||||
var result = await httpClient.PostAsJsonAsync("v1/Auth/register", registerRequest);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@ using Microsoft.AspNetCore.Identity;
|
||||
/// </summary>
|
||||
public class AliasVaultUser : IdentityUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the SRP identity used for authentication. This is a fixed value (typically a random GUID)
|
||||
/// that is used for all SRP operations, is set during registration, and never changes.
|
||||
/// </summary>
|
||||
[System.ComponentModel.DataAnnotations.StringLength(255)]
|
||||
public string? SrpIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets created timestamp.
|
||||
/// </summary>
|
||||
|
||||
989
apps/server/Databases/AliasServerDb/Migrations/20251211120421_AddSrpIdentityToUser.Designer.cs
generated
Normal file
989
apps/server/Databases/AliasServerDb/Migrations/20251211120421_AddSrpIdentityToUser.Designer.cs
generated
Normal file
@@ -0,0 +1,989 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using AliasServerDb;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
[DbContext(typeof(AliasServerDbContext))]
|
||||
[Migration("20251211120421_AddSrpIdentityToUser")]
|
||||
partial class AddSrpIdentityToUser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.4")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true)
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
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("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastPasswordChanged")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
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("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
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<bool>("Blocked")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("EmailsReceived")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime?>("LastActivityDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("MaxEmailAgeDays")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("MaxEmails")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("PasswordChangedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SrpIdentity")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AliasVaultUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DeviceIdentifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("ExpireDate")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(45)
|
||||
.HasColumnType("character varying(45)");
|
||||
|
||||
b.Property<string>("PreviousTokenValue")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AliasVaultUserRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AuthLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AdditionalInfo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Browser")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Client")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Country")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("DeviceType")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<int>("EventType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("FailureReason")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<bool>("IsSuccess")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsSuspiciousActivity")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("OperatingSystem")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("RequestPath")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "EventType" }, "IX_EventType");
|
||||
|
||||
b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress");
|
||||
|
||||
b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp");
|
||||
|
||||
b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp")
|
||||
.IsDescending(false, false, true);
|
||||
|
||||
b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp")
|
||||
.IsDescending(false, true);
|
||||
|
||||
b.ToTable("AuthLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Email", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("DateSystem")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EncryptedSymmetricKey")
|
||||
.IsRequired()
|
||||
.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("boolean");
|
||||
|
||||
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("uuid");
|
||||
|
||||
b.Property<bool>("Visible")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
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");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<byte[]>("Bytes")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
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");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Application")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Exception")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Level")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
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<string>("SourceContext")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Application");
|
||||
|
||||
b.HasIndex("TimeStamp");
|
||||
|
||||
b.ToTable("Logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.MobileLoginRequest", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("ClearedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ClientIpAddress")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClientPublicKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EncryptedDecryptionKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("FulfilledAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("MobileIpAddress")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("RetrievedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ClientIpAddress" }, "IX_ClientIpAddress");
|
||||
|
||||
b.HasIndex(new[] { "CreatedAt" }, "IX_CreatedAt");
|
||||
|
||||
b.HasIndex(new[] { "MobileIpAddress" }, "IX_MobileIpAddress");
|
||||
|
||||
b.HasIndex(new[] { "RetrievedAt", "ClearedAt", "FulfilledAt" }, "IX_RetrievedAt_ClearedAt_FulfilledAt");
|
||||
|
||||
b.HasIndex(new[] { "UserId" }, "IX_UserId");
|
||||
|
||||
b.ToTable("MobileLoginRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.ServerSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("ServerSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<TimeOnly?>("EndTime")
|
||||
.HasColumnType("time without time zone");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsOnDemand")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<DateTime>("RunDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<TimeOnly>("StartTime")
|
||||
.HasColumnType("time without time zone");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TaskRunnerJobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Address")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("AddressDomain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("AddressLocal")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("Disabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Address")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId", "Disabled");
|
||||
|
||||
b.ToTable("UserEmailClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("IsPrimary")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("PublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserEncryptionKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Vault", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Client")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("CredentialsCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("EmailClaimsCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("EncryptionSettings")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("EncryptionType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("FileSize")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("RevisionNumber")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("VaultBlob")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Verifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Vaults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CurrentStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("DesiredStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<DateTime>("Heartbeat")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ServiceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkerServiceStatuses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Xml")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataProtectionKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
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.Cascade)
|
||||
.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.MobileLoginRequest", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.UserEmailClaim", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany("EmailClaims")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSrpIdentityToUser : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SrpIdentity",
|
||||
table: "AliasVaultUsers",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: true);
|
||||
|
||||
// Populate SrpIdentity for existing users using their current username which was
|
||||
// used before this migration was applied. All newly registered users will have
|
||||
// this field populated with a random GUID generated by the client upon registration.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE ""AliasVaultUsers""
|
||||
SET ""SrpIdentity"" = LOWER(""UserName"")
|
||||
WHERE ""SrpIdentity"" IS NULL;
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SrpIdentity",
|
||||
table: "AliasVaultUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,10 @@ namespace AliasServerDb.Migrations
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SrpIdentity")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
|
||||
@@ -21,12 +21,14 @@ public class LoginInitiateResponse
|
||||
/// <param name="serverEphemeral">Server ephemeral.</param>
|
||||
/// <param name="encryptionType">Encryption type.</param>
|
||||
/// <param name="encryptionSettings">Encryption settings.</param>
|
||||
public LoginInitiateResponse(string salt, string serverEphemeral, string encryptionType, string encryptionSettings)
|
||||
/// <param name="srpIdentity">The SRP identity to use for authentication (optional for backward compatibility).</param>
|
||||
public LoginInitiateResponse(string salt, string serverEphemeral, string encryptionType, string encryptionSettings, string? srpIdentity = null)
|
||||
{
|
||||
Salt = salt;
|
||||
ServerEphemeral = serverEphemeral;
|
||||
EncryptionType = encryptionType;
|
||||
EncryptionSettings = encryptionSettings;
|
||||
SrpIdentity = srpIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,4 +54,12 @@ public class LoginInitiateResponse
|
||||
/// </summary>
|
||||
[JsonPropertyName("encryptionSettings")]
|
||||
public string EncryptionSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SRP identity to use for authentication. This is a fixed value that doesn't change
|
||||
/// even if the display username is updated. Clients should use this value (instead of the typed username)
|
||||
/// for all SRP operations.
|
||||
/// </summary>
|
||||
[JsonPropertyName("srpIdentity")]
|
||||
public string? SrpIdentity { get; set; }
|
||||
}
|
||||
|
||||
@@ -20,13 +20,15 @@ public class RegisterRequest
|
||||
/// <param name="verifier">The verifier value.</param>
|
||||
/// <param name="encryptionType">The encryption type.</param>
|
||||
/// <param name="encryptionSettings">The encryption settings.</param>
|
||||
public RegisterRequest(string username, string salt, string verifier, string encryptionType, string encryptionSettings)
|
||||
/// <param name="srpIdentity">The SRP identity.</param>
|
||||
public RegisterRequest(string username, string salt, string verifier, string encryptionType, string encryptionSettings, string? srpIdentity = null)
|
||||
{
|
||||
Username = username.ToLowerInvariant().Trim();
|
||||
Salt = salt;
|
||||
Verifier = verifier;
|
||||
EncryptionType = encryptionType;
|
||||
EncryptionSettings = encryptionSettings;
|
||||
SrpIdentity = srpIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,4 +55,11 @@ public class RegisterRequest
|
||||
/// Gets the encryption settings.
|
||||
/// </summary>
|
||||
public string EncryptionSettings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SRP identity used for authentication. This is a fixed value (typically a GUID) that
|
||||
/// is used for all SRP operations. If not provided, defaults to the lowercase username for
|
||||
/// backward compatibility.
|
||||
/// </summary>
|
||||
public string? SrpIdentity { get; }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace AliasVault.Shared.Models.WebApi.PasswordChange;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a login response.
|
||||
/// Represents a password change initiate response.
|
||||
/// </summary>
|
||||
public class PasswordChangeInitiateResponse
|
||||
{
|
||||
@@ -21,12 +21,14 @@ public class PasswordChangeInitiateResponse
|
||||
/// <param name="serverEphemeral">Server ephemeral.</param>
|
||||
/// <param name="encryptionType">Encryption type.</param>
|
||||
/// <param name="encryptionSettings">Encryption settings.</param>
|
||||
public PasswordChangeInitiateResponse(string salt, string serverEphemeral, string encryptionType, string encryptionSettings)
|
||||
/// <param name="srpIdentity">The SRP identity.</param>
|
||||
public PasswordChangeInitiateResponse(string salt, string serverEphemeral, string encryptionType, string encryptionSettings, string? srpIdentity = null)
|
||||
{
|
||||
Salt = salt;
|
||||
ServerEphemeral = serverEphemeral;
|
||||
EncryptionType = encryptionType;
|
||||
EncryptionSettings = encryptionSettings;
|
||||
SrpIdentity = srpIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,4 +54,11 @@ public class PasswordChangeInitiateResponse
|
||||
/// </summary>
|
||||
[JsonPropertyName("encryptionSettings")]
|
||||
public string EncryptionSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SRP identity to use for authentication. This is a fixed value that doesn't change
|
||||
/// even if the display username is updated. Clients should use this value for all SRP operations.
|
||||
/// </summary>
|
||||
[JsonPropertyName("srpIdentity")]
|
||||
public string? SrpIdentity { get; set; }
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export type LoginResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ export type PasswordChangeInitiateResponse = {
|
||||
serverEphemeral: string;
|
||||
encryptionType: string;
|
||||
encryptionSettings: string;
|
||||
srpIdentity?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user