mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-07-31 09:17:11 -04:00
Add rate limit tables
This commit is contained in:
@@ -88,6 +88,7 @@ builder.Services.AddScoped<ServerSettingsService>();
|
||||
builder.Services.AddScoped<RegistrationRateLimitService>();
|
||||
builder.Services.AddScoped<IpBlockListService>();
|
||||
builder.Services.AddSingleton<FaviconRateLimitService>();
|
||||
builder.Services.AddScoped<RateLimitService>();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
builder.Services.AddLogging(logging =>
|
||||
|
||||
16
apps/server/AliasVault.Api/Services/EffectiveRateLimit.cs
Normal file
16
apps/server/AliasVault.Api/Services/EffectiveRateLimit.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="EffectiveRateLimit.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A resolved limit for a user: <paramref name="MaxCount"/> over a window (<paramref name="WindowSeconds"/> 0 =
|
||||
/// absolute cap, > 0 = rolling window).
|
||||
/// </summary>
|
||||
/// <param name="WindowSeconds">The rolling window length in seconds, or 0 for an absolute cap.</param>
|
||||
/// <param name="MaxCount">The maximum allowed count for the window.</param>
|
||||
public sealed record EffectiveRateLimit(int WindowSeconds, int MaxCount);
|
||||
124
apps/server/AliasVault.Api/Services/RateLimitResolver.cs
Normal file
124
apps/server/AliasVault.Api/Services/RateLimitResolver.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RateLimitResolver.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Services;
|
||||
|
||||
using AliasServerDb;
|
||||
|
||||
/// <summary>
|
||||
/// Rate limit resolver logic which determines the limits that apply to a given user.
|
||||
/// </summary>
|
||||
public static class RateLimitResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the limits that apply to the given user. For each window the most specific scope wins
|
||||
/// (per-user > per-tier > global); a winning MaxCount of 0 means unlimited.
|
||||
/// </summary>
|
||||
/// <param name="rules">The candidate rules.</param>
|
||||
/// <param name="user">The user to resolve limits for.</param>
|
||||
/// <param name="limitType">The limit type to resolve.</param>
|
||||
/// <param name="now">The current UTC time, used to evaluate effective-from/until windows.</param>
|
||||
/// <returns>The limits that must all be satisfied. Empty means no limit applies.</returns>
|
||||
public static IReadOnlyList<EffectiveRateLimit> Resolve(IEnumerable<RateLimit> rules, AliasVaultUser user, RateLimitType limitType, DateTime now)
|
||||
{
|
||||
var tier = ResolveTier(user);
|
||||
|
||||
var applicableRules = rules
|
||||
.Where(r => r.Enabled)
|
||||
.Where(r => r.LimitType == limitType)
|
||||
.Where(r => (r.EffectiveFrom is null || r.EffectiveFrom <= now) && (r.EffectiveUntil is null || r.EffectiveUntil >= now))
|
||||
.Where(r => r.AppliesToAccountAgeMaxDays is null || user.CreatedAt > now.AddDays(-r.AppliesToAccountAgeMaxDays.Value));
|
||||
|
||||
var windows = new HashSet<int>();
|
||||
var userMax = new Dictionary<int, int>();
|
||||
var tierMax = new Dictionary<int, int>();
|
||||
var globalMax = new Dictionary<int, int>();
|
||||
|
||||
foreach (var rule in applicableRules)
|
||||
{
|
||||
if (rule.UserId is not null)
|
||||
{
|
||||
if (rule.UserId == user.Id)
|
||||
{
|
||||
Record(windows, userMax, rule.WindowSeconds, rule.MaxCount);
|
||||
}
|
||||
}
|
||||
else if (rule.Tier is not null)
|
||||
{
|
||||
if (rule.Tier == tier)
|
||||
{
|
||||
Record(windows, tierMax, rule.WindowSeconds, rule.MaxCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Record(windows, globalMax, rule.WindowSeconds, rule.MaxCount);
|
||||
}
|
||||
}
|
||||
|
||||
var result = new List<EffectiveRateLimit>();
|
||||
foreach (var window in windows)
|
||||
{
|
||||
int max;
|
||||
if (userMax.TryGetValue(window, out var um))
|
||||
{
|
||||
max = um;
|
||||
}
|
||||
else if (tierMax.TryGetValue(window, out var tm))
|
||||
{
|
||||
max = tm;
|
||||
}
|
||||
else if (globalMax.TryGetValue(window, out var gm))
|
||||
{
|
||||
max = gm;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 0 = unlimited, so not enforced.
|
||||
if (max > 0)
|
||||
{
|
||||
result.Add(new EffectiveRateLimit(window, max));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the user's tier. Tiers are not implemented yet, so every user is <see cref="AccountTier.Free"/> by default.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to resolve the tier for.</param>
|
||||
/// <returns>The account tier that applies to the user.</returns>
|
||||
public static AccountTier ResolveTier(AliasVaultUser user) => AccountTier.Free;
|
||||
|
||||
/// <summary>
|
||||
/// Records a candidate MaxCount for a scope+window. A concrete limit wins over unlimited (0), and between
|
||||
/// multiple concrete limits the most restrictive (smallest) is kept.
|
||||
/// </summary>
|
||||
private static void Record(HashSet<int> windows, Dictionary<int, int> map, int window, int max)
|
||||
{
|
||||
windows.Add(window);
|
||||
|
||||
if (!map.TryGetValue(window, out var existing))
|
||||
{
|
||||
map[window] = max;
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing == 0)
|
||||
{
|
||||
map[window] = max;
|
||||
}
|
||||
else if (max > 0)
|
||||
{
|
||||
map[window] = Math.Min(existing, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
apps/server/AliasVault.Api/Services/RateLimitService.cs
Normal file
60
apps/server/AliasVault.Api/Services/RateLimitService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RateLimitService.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Api.Services;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Shared.Providers.Time;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the rate-limit rules that apply to a given user. Rules are read from the RateLimits table and cached
|
||||
/// in memory briefly so the vault-sync hot path does not hit the database on every request.
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">IDbContextFactory instance.</param>
|
||||
/// <param name="cache">IMemoryCache instance used to cache the enabled rules.</param>
|
||||
/// <param name="timeProvider">ITimeProvider instance.</param>
|
||||
public class RateLimitService(IAliasServerDbContextFactory dbContextFactory, IMemoryCache cache, ITimeProvider timeProvider)
|
||||
{
|
||||
private const int CacheDurationSeconds = 60;
|
||||
|
||||
private const string EnabledRulesCacheKey = "RateLimits_EnabledRules";
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the limits that apply to the given user for the given limit type.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to resolve limits for.</param>
|
||||
/// <param name="limitType">The limit type to resolve.</param>
|
||||
/// <returns>The limits that must all be satisfied. Empty means no limit applies.</returns>
|
||||
public async Task<IReadOnlyList<EffectiveRateLimit>> ResolveAsync(AliasVaultUser user, RateLimitType limitType)
|
||||
{
|
||||
var rules = await GetEnabledRulesAsync();
|
||||
return RateLimitResolver.Resolve(rules, user, limitType, timeProvider.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the enabled rules from cache, refreshing at most once every <see cref="CacheDurationSeconds"/>.
|
||||
/// </summary>
|
||||
/// <returns>The list of enabled rules.</returns>
|
||||
private async Task<List<RateLimit>> GetEnabledRulesAsync()
|
||||
{
|
||||
if (cache.TryGetValue(EnabledRulesCacheKey, out List<RateLimit>? cached) && cached is not null)
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
await using var dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
var rules = await dbContext.RateLimits
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Enabled)
|
||||
.ToListAsync();
|
||||
|
||||
cache.Set(EnabledRulesCacheKey, rules, TimeSpan.FromSeconds(CacheDurationSeconds));
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
25
apps/server/Databases/AliasServerDb/AccountTier.cs
Normal file
25
apps/server/Databases/AliasServerDb/AccountTier.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="AccountTier.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasServerDb;
|
||||
|
||||
/// <summary>
|
||||
/// Subscription tier of an account, used to target a <see cref="RateLimit"/> rule at a class of users.
|
||||
/// Tiers are not implemented yet, acts as placeholder for future implementation.
|
||||
/// </summary>
|
||||
public enum AccountTier
|
||||
{
|
||||
/// <summary>
|
||||
/// The default tier for all accounts.
|
||||
/// </summary>
|
||||
Free = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Premium (paid) tier. Not yet implemented, acts as placeholder for unit tests.
|
||||
/// </summary>
|
||||
Premium = 1,
|
||||
}
|
||||
@@ -147,6 +147,11 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
/// </summary>
|
||||
public DbSet<BlockedIpRange> BlockedIpRanges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the RateLimits DbSet.
|
||||
/// </summary>
|
||||
public DbSet<RateLimit> RateLimits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the connection string if it is not already configured.
|
||||
/// </summary>
|
||||
@@ -285,5 +290,12 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Configure RateLimit - AliasVaultUser relationship
|
||||
modelBuilder.Entity<RateLimit>()
|
||||
.HasOne(r => r.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,6 @@ public class AliasVaultUser : IdentityUser
|
||||
/// </summary>
|
||||
public int MaxEmailAgeDays { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the per-user alias creation limit. 0 means no per-user limit (global settings may apply).
|
||||
/// A value greater than 0 is a explicit per-user cap that always applies to this account and takes priority
|
||||
/// over any other global limit.
|
||||
/// </summary>
|
||||
public int MaxAliases { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date of the user's last activity (login, API call, etc.).
|
||||
/// Updated automatically on successful authentication events.
|
||||
|
||||
1118
apps/server/Databases/AliasServerDb/Migrations/20260624212335_AddRateLimits.Designer.cs
generated
Normal file
1118
apps/server/Databases/AliasServerDb/Migrations/20260624212335_AddRateLimits.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRateLimits : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxAliases",
|
||||
table: "AliasVaultUsers");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RateLimits",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
LimitType = table.Column<int>(type: "integer", nullable: false),
|
||||
UserId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
Tier = table.Column<int>(type: "integer", nullable: true),
|
||||
WindowSeconds = table.Column<int>(type: "integer", nullable: false),
|
||||
MaxCount = table.Column<int>(type: "integer", nullable: false),
|
||||
AppliesToAccountAgeMaxDays = table.Column<int>(type: "integer", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
EffectiveFrom = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
EffectiveUntil = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
CreatedBy = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RateLimits", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RateLimits_AliasVaultUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AliasVaultUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserEmailClaims_UserId_CreatedAt",
|
||||
table: "UserEmailClaims",
|
||||
columns: new[] { "UserId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RateLimits_LimitType_Enabled",
|
||||
table: "RateLimits",
|
||||
columns: new[] { "LimitType", "Enabled" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RateLimits_Tier",
|
||||
table: "RateLimits",
|
||||
column: "Tier");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RateLimits_UserId",
|
||||
table: "RateLimits",
|
||||
column: "UserId");
|
||||
|
||||
// Remove obsolete settings that are now handled by this new RateLimits table.
|
||||
migrationBuilder.Sql(
|
||||
"DELETE FROM \"ServerSettings\" WHERE \"Key\" IN ('MaxAliasesForNewAccounts', 'NewAccountAliasLimitDays');");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RateLimits");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserEmailClaims_UserId_CreatedAt",
|
||||
table: "UserEmailClaims");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MaxAliases",
|
||||
table: "AliasVaultUsers",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,9 +156,6 @@ namespace AliasServerDb.Migrations
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("MaxAliases")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("MaxEmailAgeDays")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@@ -604,6 +601,65 @@ namespace AliasServerDb.Migrations
|
||||
b.ToTable("MobileLoginRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.RateLimit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("AppliesToAccountAgeMaxDays")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime?>("EffectiveFrom")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime?>("EffectiveUntil")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("LimitType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("MaxCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<int?>("Tier")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<int>("WindowSeconds")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Tier");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("LimitType", "Enabled");
|
||||
|
||||
b.ToTable("RateLimits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.ServerSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
@@ -699,6 +755,8 @@ namespace AliasServerDb.Migrations
|
||||
b.HasIndex("Address")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId", "CreatedAt");
|
||||
|
||||
b.HasIndex("UserId", "Disabled");
|
||||
|
||||
b.ToTable("UserEmailClaims");
|
||||
@@ -991,6 +1049,16 @@ namespace AliasServerDb.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.RateLimit", 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")
|
||||
|
||||
108
apps/server/Databases/AliasServerDb/RateLimit.cs
Normal file
108
apps/server/Databases/AliasServerDb/RateLimit.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RateLimit.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasServerDb;
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// A rate-limit / quota rule used by the API to throttle different types of usage.
|
||||
/// - <see cref="UserId"/> set = per-user,
|
||||
/// - <see cref="Tier"/> set = per-tier,
|
||||
/// - else global (most specific wins).
|
||||
/// - <see cref="WindowSeconds"/> 0 = global maximum (all-time). > 0 = max allowed within a rolling window.
|
||||
/// - <see cref="MaxCount"/> 0 = unlimited.
|
||||
/// </summary>
|
||||
[Index(nameof(LimitType), nameof(Enabled))]
|
||||
[Index(nameof(UserId))]
|
||||
[Index(nameof(Tier))]
|
||||
public class RateLimit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the rule.
|
||||
/// </summary>
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action this rule governs.
|
||||
/// </summary>
|
||||
public RateLimitType LimitType { get; set; } = RateLimitType.AliasCreation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user this rule applies to (per-user override). Null for tier-level and global rules.
|
||||
/// </summary>
|
||||
[StringLength(255)]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the navigation property to the user this rule applies to.
|
||||
/// </summary>
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public virtual AliasVaultUser? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the account tier this rule applies to. Null for per-user and global rules.
|
||||
/// </summary>
|
||||
public AccountTier? Tier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rolling window length in seconds (0 = absolute cap on currently-held aliases).
|
||||
/// </summary>
|
||||
public int WindowSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed count for the window (0 = unlimited).
|
||||
/// </summary>
|
||||
public int MaxCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the account age (in days) below which the rule applies; null = applies regardless of age.
|
||||
/// Used to restrict new accounts more tightly (the limit lifts once the account is older).
|
||||
/// </summary>
|
||||
public int? AppliesToAccountAgeMaxDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this rule is enforced. Disabled rules are retained for auditing.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional note describing why the rule exists.
|
||||
/// </summary>
|
||||
[MaxLength(1000)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional UTC timestamp before which the rule is not enforced.
|
||||
/// </summary>
|
||||
public DateTime? EffectiveFrom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional UTC timestamp after which the rule is no longer enforced.
|
||||
/// </summary>
|
||||
public DateTime? EffectiveUntil { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional identifier of who created the rule.
|
||||
/// </summary>
|
||||
[MaxLength(255)]
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation date of the rule.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last update date of the rule.
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
19
apps/server/Databases/AliasServerDb/RateLimitType.cs
Normal file
19
apps/server/Databases/AliasServerDb/RateLimitType.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RateLimitType.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasServerDb;
|
||||
|
||||
/// <summary>
|
||||
/// The action a <see cref="RateLimit"/> rule governs.
|
||||
/// </summary>
|
||||
public enum RateLimitType
|
||||
{
|
||||
/// <summary>
|
||||
/// Limits the creation of new email aliases.
|
||||
/// </summary>
|
||||
AliasCreation = 0,
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// </summary>
|
||||
[Index(nameof(Address), IsUnique = true)]
|
||||
[Index(nameof(UserId), nameof(Disabled))]
|
||||
[Index(nameof(UserId), nameof(CreatedAt))]
|
||||
public class UserEmailClaim
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -84,17 +84,4 @@ public class ServerSettingsModel
|
||||
/// Set to 0 to disable IP-based registration rate limiting.
|
||||
/// </summary>
|
||||
public int MaxRegistrationsPerIpPer24Hours { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the age (in days) below which an account is considered "new" for alias-creation limiting purposes.
|
||||
/// Defaults to 0 (disabled). When 0, or when MaxAliasesForNewAccounts is 0, no new-account alias limit is applied.
|
||||
/// </summary>
|
||||
public int NewAccountAliasLimitDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of active aliases a "new" account (younger than NewAccountAliasLimitDays) may
|
||||
/// have. Defaults to 0 (unlimited). When the limit is reached, additional alias creation is silently skipped
|
||||
/// during vault sync.
|
||||
/// </summary>
|
||||
public int MaxAliasesForNewAccounts { get; set; }
|
||||
}
|
||||
|
||||
@@ -201,16 +201,6 @@ public class ServerSettingsService(IAliasServerDbContextFactory dbContextFactory
|
||||
model.MobileLoginLogRetentionDays = mobileLoginDays;
|
||||
}
|
||||
|
||||
if (int.TryParse(settings.GetValueOrDefault("NewAccountAliasLimitDays"), out var newAccountAliasLimitDays))
|
||||
{
|
||||
model.NewAccountAliasLimitDays = newAccountAliasLimitDays;
|
||||
}
|
||||
|
||||
if (int.TryParse(settings.GetValueOrDefault("MaxAliasesForNewAccounts"), out var maxAliasesForNewAccounts))
|
||||
{
|
||||
model.MaxAliasesForNewAccounts = maxAliasesForNewAccounts;
|
||||
}
|
||||
|
||||
cache.Set(AllSettingsCacheKey, model, TimeSpan.FromSeconds(AllSettingsCacheDurationSeconds));
|
||||
|
||||
return model;
|
||||
@@ -236,7 +226,5 @@ public class ServerSettingsService(IAliasServerDbContextFactory dbContextFactory
|
||||
await SetSettingAsync("RefreshTokenLifetimeLong", model.RefreshTokenLifetimeLong.ToString());
|
||||
await SetSettingAsync("MaxRegistrationsPerIpPer24Hours", model.MaxRegistrationsPerIpPer24Hours.ToString());
|
||||
await SetSettingAsync("MobileLoginLogRetentionDays", model.MobileLoginLogRetentionDays.ToString());
|
||||
await SetSettingAsync("NewAccountAliasLimitDays", model.NewAccountAliasLimitDays.ToString());
|
||||
await SetSettingAsync("MaxAliasesForNewAccounts", model.MaxAliasesForNewAccounts.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user