Add IP block tables (#2131)

This commit is contained in:
Leendert de Borst
2026-06-04 19:50:09 +02:00
committed by Leendert de Borst
parent a60d0966e6
commit f0db97f4c2
7 changed files with 1254 additions and 0 deletions

View File

@@ -142,6 +142,11 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
/// </summary>
public DbSet<MobileLoginRequest> MobileLoginRequests { get; set; }
/// <summary>
/// Gets or sets the BlockedIpRanges DbSet.
/// </summary>
public DbSet<BlockedIpRange> BlockedIpRanges { get; set; }
/// <summary>
/// Sets up the connection string if it is not already configured.
/// </summary>

View File

@@ -36,6 +36,11 @@ public class AliasVaultUser : IdentityUser
/// </summary>
public bool Blocked { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user is marked as shadow-blocked.
/// </summary>
public bool ShadowBlocked { get; set; }
/// <summary>
/// Gets or sets updated timestamp.
/// </summary>

View File

@@ -57,6 +57,11 @@ public enum AuthFailureReason
/// </summary>
RegistrationRateLimitExceeded = 8,
/// <summary>
/// Indicates that the attempt was blocked because the originating IP address is on the blocklist.
/// </summary>
IpBlocked = 9,
/// <summary>
/// Indicates that the failure reason was unknown.
/// </summary>

View File

@@ -0,0 +1,80 @@
//-----------------------------------------------------------------------
// <copyright file="BlockedIpRange.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 Microsoft.EntityFrameworkCore;
/// <summary>
/// Represents a blocked IP address range. Ranges are stored in CIDR notation (e.g. "1.2.3.4/32",
/// "1.2.3.0/24", "1.2.0.0/16", "1.0.0.0/8") and are matched against the raw IP address
/// of an incoming request.
/// </summary>
[Index(nameof(IpRange), Name = "IX_BlockedIpRange_IpRange", IsUnique = true)]
[Index(nameof(Enabled), Name = "IX_BlockedIpRange_Enabled")]
public class BlockedIpRange
{
/// <summary>
/// Gets or sets the unique identifier for the blocked IP range entry.
/// </summary>
[Key]
public int Id { get; set; }
/// <summary>
/// Gets or sets the IP range in normalized CIDR notation (e.g. "1.2.3.0/24"). A single address is
/// stored with a full prefix length (/32 for IPv4, /128 for IPv6).
/// </summary>
[Required]
[MaxLength(50)]
public string IpRange { get; set; } = null!;
/// <summary>
/// Gets or sets a value indicating whether requests from this range are blocked from registering new accounts.
/// </summary>
public bool BlockRegistration { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether requests from this range are blocked from logging in / general access.
/// </summary>
public bool BlockLogin { get; set; }
/// <summary>
/// Gets or sets a value indicating whether requests from this range are shadow blocked. A shadow blocked user
/// will be limited in certain ways, such as not being able to retrieve emails.
/// </summary>
public bool BlockShadow { get; set; }
/// <summary>
/// Gets or sets an optional free-text reason / note describing why the range was blocked.
/// </summary>
[MaxLength(500)]
public string? Reason { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this blocklist entry is currently active. Disabled entries are
/// retained for auditing but are not enforced.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Gets or sets an optional identifier of who created the entry (e.g. admin username or "system").
/// </summary>
[MaxLength(255)]
public string? CreatedBy { get; set; }
/// <summary>
/// Gets or sets the creation date of the blocklist entry.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets the last update date of the blocklist entry.
/// </summary>
public DateTime UpdatedAt { get; set; }
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace AliasServerDb.Migrations
{
/// <inheritdoc />
public partial class AddIpBlocklistAndShadowBlock : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ShadowBlocked",
table: "AliasVaultUsers",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "BlockedIpRanges",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
IpRange = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
BlockRegistration = table.Column<bool>(type: "boolean", nullable: false),
BlockLogin = table.Column<bool>(type: "boolean", nullable: false),
BlockShadow = table.Column<bool>(type: "boolean", nullable: false),
Reason = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
Enabled = table.Column<bool>(type: "boolean", nullable: false),
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_BlockedIpRanges", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_BlockedIpRange_Enabled",
table: "BlockedIpRanges",
column: "Enabled");
migrationBuilder.CreateIndex(
name: "IX_BlockedIpRange_IpRange",
table: "BlockedIpRanges",
column: "IpRange",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlockedIpRanges");
migrationBuilder.DropColumn(
name: "ShadowBlocked",
table: "AliasVaultUsers");
}
}
}

View File

@@ -180,6 +180,9 @@ namespace AliasServerDb.Migrations
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("ShadowBlocked")
.HasColumnType("boolean");
b.Property<string>("SrpIdentity")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
@@ -317,6 +320,55 @@ namespace AliasServerDb.Migrations
b.ToTable("AuthLogs");
});
modelBuilder.Entity("AliasServerDb.BlockedIpRange", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("BlockLogin")
.HasColumnType("boolean");
b.Property<bool>("BlockRegistration")
.HasColumnType("boolean");
b.Property<bool>("BlockShadow")
.HasColumnType("boolean");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedBy")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("IpRange")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Reason")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex(new[] { "Enabled" }, "IX_BlockedIpRange_Enabled");
b.HasIndex(new[] { "IpRange" }, "IX_BlockedIpRange_IpRange")
.IsUnique();
b.ToTable("BlockedIpRanges");
});
modelBuilder.Entity("AliasServerDb.Email", b =>
{
b.Property<int>("Id")