mirror of
https://github.com/aliasvault/aliasvault.git
synced 2025-12-30 09:38:02 -05:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70220cecbb | ||
|
|
c63faa352f | ||
|
|
7e261a05c9 | ||
|
|
545ec5576e | ||
|
|
73dcbe5860 | ||
|
|
13917444b9 | ||
|
|
119e13a9dd | ||
|
|
7d656e9a9a | ||
|
|
8bd05b5c2e | ||
|
|
1e65f14323 | ||
|
|
2c7543889d | ||
|
|
63c5483208 | ||
|
|
2586d61651 | ||
|
|
c7a32cf0e9 | ||
|
|
46cc6527aa |
@@ -6,9 +6,9 @@
|
||||
<a href="https://app.aliasvault.net">Live demo 🔥</a> • <a href="https://aliasvault.net?utm_source=gh-readme">Website 🌐</a> • <a href="https://docs.aliasvault.net?utm_source=gh-readme">Documentation 📚</a> • <a href="#installation">Installation ⚙️</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
Open-source password and alias manager
|
||||
</h3>
|
||||
<p align="center">
|
||||
<strong>Open-source password and alias manager</strong>
|
||||
</p>
|
||||
|
||||
[<img src="https://img.shields.io/github/v/release/lanedirt/AliasVault?include_prereleases&logo=github">](https://github.com/lanedirt/AliasVault/releases)
|
||||
[<img src="https://img.shields.io/github/actions/workflow/status/lanedirt/AliasVault/docker-compose-build.yml?label=docker-compose%20build">](https://github.com/lanedirt/AliasVault/actions/workflows/docker-compose-build.yml)
|
||||
@@ -25,7 +25,7 @@ Open-source password and alias manager
|
||||
|
||||
</div>
|
||||
|
||||
AliasVault is an open-source password and alias manager built with C# ASP.NET technology. AliasVault can be self-hosted on your own server with Docker, providing a secure and private solution for managing your online identities and passwords.
|
||||
AliasVault is an end-to-end encrypted password and alias manager that protects your privacy by creating alternative identities, passwords and email addresses for every website you use. The core of AliasVault is built with C# ASP.NET Blazor WASM technology. AliasVault can be self-hosted on your own server with Docker.
|
||||
|
||||
### What makes AliasVault unique:
|
||||
- **Zero-knowledge architecture**: All data is end-to-end encrypted on the client and stored in encrypted state on the server. Your master password never leaves your device and the server never has access to your data.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# @version 0.9.0
|
||||
# @version 0.9.3
|
||||
|
||||
# Repository information used for downloading files and images from GitHub
|
||||
REPO_OWNER="lanedirt"
|
||||
@@ -1295,6 +1295,7 @@ handle_install_version() {
|
||||
"${GITHUB_CONTAINER_REGISTRY}-client:${target_version}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-admin:${target_version}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-smtp:${target_version}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-task-runner:${target_version}"
|
||||
)
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
|
||||
6
src/AliasVault.Admin/package-lock.json
generated
6
src/AliasVault.Admin/package-lock.json
generated
@@ -710,9 +710,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<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.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
6
src/AliasVault.Client/package-lock.json
generated
6
src/AliasVault.Client/package-lock.json
generated
@@ -710,9 +710,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SpamOK.PasswordGenerator" Version="1.0.1" />
|
||||
<PackageReference Include="SpamOK.PasswordGenerator" Version="1.1.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -30,7 +30,7 @@ public static class AppInfo
|
||||
/// <summary>
|
||||
/// Gets the patch version number.
|
||||
/// </summary>
|
||||
public const int VersionPatch = 2;
|
||||
public const int VersionPatch = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the build number, typically used in CI/CD pipelines.
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ServerSettingsModel
|
||||
public int AuthLogRetentionDays { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email retention days. Defaults to 0 (disabled).
|
||||
/// Gets or sets the email retention days. Defaults to 0 (unlimited).
|
||||
/// </summary>
|
||||
public int EmailRetentionDays { get; set; }
|
||||
|
||||
|
||||
@@ -99,19 +99,53 @@ public class ServerSettingsService(IDbContextFactory<AliasServerDbContext> dbCon
|
||||
await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None);
|
||||
var settings = await dbContext.ServerSettings.ToDictionaryAsync(x => x.Key, x => x.Value);
|
||||
|
||||
return new ServerSettingsModel
|
||||
// Create model with defaults
|
||||
var model = new ServerSettingsModel();
|
||||
|
||||
// Only override if parsing succeeds
|
||||
if (int.TryParse(settings.GetValueOrDefault("GeneralLogRetentionDays"), out var generalDays))
|
||||
{
|
||||
GeneralLogRetentionDays = int.TryParse(settings.GetValueOrDefault("GeneralLogRetentionDays"), out var generalDays) ? generalDays : 30,
|
||||
AuthLogRetentionDays = int.TryParse(settings.GetValueOrDefault("AuthLogRetentionDays"), out var authDays) ? authDays : 90,
|
||||
EmailRetentionDays = int.TryParse(settings.GetValueOrDefault("EmailRetentionDays"), out var emailDays) ? emailDays : 30,
|
||||
MaxEmailsPerUser = int.TryParse(settings.GetValueOrDefault("MaxEmailsPerUser"), out var maxEmails) ? maxEmails : 100,
|
||||
MaintenanceTime = TimeOnly.TryParse(
|
||||
settings.GetValueOrDefault("MaintenanceTime") ?? "00:00",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out var time) ? time : new TimeOnly(0, 0),
|
||||
TaskRunnerDays = settings.GetValueOrDefault("TaskRunnerDays")?.Split(',').Select(int.Parse).ToList() ?? new List<int> { 1, 2, 3, 4, 5, 6, 7 },
|
||||
};
|
||||
model.GeneralLogRetentionDays = generalDays;
|
||||
}
|
||||
|
||||
if (int.TryParse(settings.GetValueOrDefault("AuthLogRetentionDays"), out var authDays))
|
||||
{
|
||||
model.AuthLogRetentionDays = authDays;
|
||||
}
|
||||
|
||||
if (int.TryParse(settings.GetValueOrDefault("EmailRetentionDays"), out var emailDays))
|
||||
{
|
||||
model.EmailRetentionDays = emailDays;
|
||||
}
|
||||
|
||||
if (int.TryParse(settings.GetValueOrDefault("MaxEmailsPerUser"), out var maxEmails))
|
||||
{
|
||||
model.MaxEmailsPerUser = maxEmails;
|
||||
}
|
||||
|
||||
if (TimeOnly.TryParse(
|
||||
settings.GetValueOrDefault("MaintenanceTime") ?? "00:00",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out var time))
|
||||
{
|
||||
model.MaintenanceTime = time;
|
||||
}
|
||||
|
||||
var taskRunnerDaysStr = settings.GetValueOrDefault("TaskRunnerDays");
|
||||
if (!string.IsNullOrEmpty(taskRunnerDaysStr))
|
||||
{
|
||||
try
|
||||
{
|
||||
model.TaskRunnerDays = taskRunnerDaysStr.Split(',').Select(int.Parse).ToList();
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Keep default if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -68,11 +68,49 @@ public class AuthTests : ClientPlaywrightTest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if logging out and logging in works.
|
||||
/// Test if logging in with different case variations of username works.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
[Order(3)]
|
||||
public async Task CapitalizedUsernameTest()
|
||||
{
|
||||
// Logout current user
|
||||
await Logout();
|
||||
|
||||
// Create a new user with capital letters in username
|
||||
var capitalUsername = "TestUser@Example.com";
|
||||
await Register(checkForSuccess: true, username: capitalUsername);
|
||||
await Logout();
|
||||
|
||||
// Test Case 1: Try to login with lowercase version of the username
|
||||
var lowercaseUsername = capitalUsername.ToLower();
|
||||
await LoginWithUsername(lowercaseUsername);
|
||||
await VerifySuccessfulLogin();
|
||||
|
||||
// Test Case 2: Try to login with exact capitalized username
|
||||
await Logout();
|
||||
await LoginWithUsername(capitalUsername);
|
||||
await VerifySuccessfulLogin();
|
||||
|
||||
// Test Case 3: Create new user with lowercase
|
||||
await Logout();
|
||||
var lowercaseUser = "testuser2@example.com";
|
||||
await Register(checkForSuccess: true, username: lowercaseUser);
|
||||
await Logout();
|
||||
|
||||
// Try logging in with uppercase version
|
||||
var uppercaseVersion = lowercaseUser.ToUpper();
|
||||
await LoginWithUsername(uppercaseVersion);
|
||||
await VerifySuccessfulLogin();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if logging out and logging in works.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
[Order(4)]
|
||||
public async Task LogoutAndLoginRememberMeTest()
|
||||
{
|
||||
await Logout();
|
||||
@@ -101,7 +139,7 @@ public class AuthTests : ClientPlaywrightTest
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
[Order(4)]
|
||||
[Order(5)]
|
||||
public async Task RegisterFormWarningTest()
|
||||
{
|
||||
await Logout();
|
||||
@@ -116,7 +154,7 @@ public class AuthTests : ClientPlaywrightTest
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
[Order(5)]
|
||||
[Order(6)]
|
||||
public async Task PasswordAuthLockoutTest()
|
||||
{
|
||||
await Logout();
|
||||
@@ -152,4 +190,41 @@ public class AuthTests : ClientPlaywrightTest
|
||||
var pageContent = await Page.TextContentAsync("body");
|
||||
Assert.That(pageContent, Does.Contain("locked out"), "No account lockout message.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login with a given username.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to login with.</param>
|
||||
/// <returns>Async task.</returns>
|
||||
private async Task LoginWithUsername(string username)
|
||||
{
|
||||
await NavigateToLogin();
|
||||
|
||||
var emailField = await WaitForAndGetElement("input[id='email']");
|
||||
var passwordField = await WaitForAndGetElement("input[id='password']");
|
||||
await emailField.FillAsync(username);
|
||||
await passwordField.FillAsync(TestUserPassword);
|
||||
|
||||
var loginButton = await WaitForAndGetElement("button[type='submit']");
|
||||
await loginButton.ClickAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that a login was successful.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
private async Task VerifySuccessfulLogin()
|
||||
{
|
||||
// Wait for the index page to load which should show "Credentials" in the top menu.
|
||||
await WaitForUrlAsync("**", "Credentials");
|
||||
|
||||
// Check if the login was successful by verifying content.
|
||||
var pageContent = await Page.TextContentAsync("body");
|
||||
Assert.That(pageContent, Does.Contain(WelcomeMessage), "No index content after logging in.");
|
||||
|
||||
// Check if login has created an auth log entry.
|
||||
var authLogEntry = await ApiDbContext.AuthLogs.FirstOrDefaultAsync(x =>
|
||||
x.EventType == AuthEventType.Login);
|
||||
Assert.That(authLogEntry, Is.Not.Null, "Auth log entry not found in database after login.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
|
||||
@@ -26,7 +26,9 @@ public static class Srp
|
||||
/// <returns>SrpSignup model.</returns>
|
||||
public static SrpPasswordChange PasswordChangeAsync(SrpClient client, string salt, string username, string passwordHashString)
|
||||
{
|
||||
// Derive a key from the password using Argon2id
|
||||
// Derive a key from the password using Argon2id.
|
||||
// Make sure the username is lowercase as the SRP protocol is case sensitive.
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
// Signup or password change: client generates a salt and verifier.
|
||||
var privateKey = DerivePrivateKey(salt, username, passwordHashString);
|
||||
@@ -44,6 +46,9 @@ public static class Srp
|
||||
/// <returns>Private key as string.</returns>
|
||||
public static string DerivePrivateKey(string salt, string username, string passwordHashString)
|
||||
{
|
||||
// Make sure the username is lowercase as the SRP protocol is case sensitive.
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
var client = new SrpClient();
|
||||
return client.DerivePrivateKey(salt, username, passwordHashString);
|
||||
}
|
||||
@@ -80,6 +85,9 @@ public static class Srp
|
||||
/// <returns>session.</returns>
|
||||
public static SrpSession DeriveSessionClient(string privateKey, string clientSecretEphemeral, string serverEphemeralPublic, string salt, string username)
|
||||
{
|
||||
// Make sure the username is lowercase as the SRP protocol is case sensitive.
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
var client = new SrpClient();
|
||||
return client.DeriveSession(
|
||||
clientSecretEphemeral,
|
||||
@@ -101,6 +109,9 @@ public static class Srp
|
||||
/// <returns>SrpSession.</returns>
|
||||
public static SrpSession? DeriveSessionServer(string serverEphemeralSecret, string clientEphemeralPublic, string salt, string username, string verifier, string clientSessionProof)
|
||||
{
|
||||
// Make sure the username is lowercase as the SRP protocol is case sensitive.
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
try
|
||||
{
|
||||
var server = new SrpServer();
|
||||
|
||||
Reference in New Issue
Block a user