Skip to content

Commit 9510711

Browse files
author
CIS Guru
committed
security enhancements
1 parent 814b63c commit 9510711

27 files changed

Lines changed: 2226 additions & 97 deletions

File tree

1-Aquiis.Infrastructure/Data/ApplicationDbContext.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
176176
.HasForeignKey(i => i.DocumentId)
177177
.OnDelete(DeleteBehavior.SetNull);
178178

179-
entity.HasIndex(e => e.InvoiceNumber).IsUnique();
179+
// Unique constraint on (OrganizationId, InvoiceNumber) for multi-tenant isolation
180+
entity.HasIndex(e => new { e.OrganizationId, e.InvoiceNumber })
181+
.IsUnique()
182+
.HasDatabaseName("IX_Invoice_OrgId_InvoiceNumber");
183+
180184
entity.Property(e => e.Amount).HasPrecision(18, 2);
181185
entity.Property(e => e.AmountPaid).HasPrecision(18, 2);
182186

@@ -200,6 +204,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
200204
.HasForeignKey(p => p.DocumentId)
201205
.OnDelete(DeleteBehavior.SetNull);
202206

207+
// Unique constraint on (OrganizationId, PaymentNumber) for multi-tenant isolation
208+
entity.HasIndex(e => new { e.OrganizationId, e.PaymentNumber })
209+
.IsUnique()
210+
.HasDatabaseName("IX_Payment_OrgId_PaymentNumber");
211+
203212
entity.Property(e => e.Amount).HasPrecision(18, 2);
204213

205214
// Configure relationship with User
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.EntityFrameworkCore.Migrations;
2+
3+
#nullable disable
4+
5+
namespace Aquiis.Infrastructure.Migrations
6+
{
7+
/// <inheritdoc />
8+
public partial class FixInvoicePaymentUniqueIndexes : Migration
9+
{
10+
/// <inheritdoc />
11+
protected override void Up(MigrationBuilder migrationBuilder)
12+
{
13+
// Drop the incorrect single-column unique index on Invoices.InvoiceNumber
14+
// This index was preventing different organizations from using the same invoice numbers
15+
migrationBuilder.DropIndex(
16+
name: "IX_Invoices_InvoiceNumber",
17+
table: "Invoices");
18+
19+
// Create correct composite unique index on Invoices(OrganizationId, InvoiceNumber)
20+
// This allows different organizations to have the same invoice number (multi-tenant safe)
21+
migrationBuilder.CreateIndex(
22+
name: "IX_Invoice_OrgId_InvoiceNumber",
23+
table: "Invoices",
24+
columns: new[] { "OrganizationId", "InvoiceNumber" },
25+
unique: true);
26+
27+
// Create composite unique index on Payments(OrganizationId, PaymentNumber)
28+
// This ensures payment numbers are unique within each organization
29+
migrationBuilder.CreateIndex(
30+
name: "IX_Payment_OrgId_PaymentNumber",
31+
table: "Payments",
32+
columns: new[] { "OrganizationId", "PaymentNumber" },
33+
unique: true);
34+
}
35+
36+
/// <inheritdoc />
37+
protected override void Down(MigrationBuilder migrationBuilder)
38+
{
39+
// Drop the composite unique indexes
40+
migrationBuilder.DropIndex(
41+
name: "IX_Invoice_OrgId_InvoiceNumber",
42+
table: "Invoices");
43+
44+
migrationBuilder.DropIndex(
45+
name: "IX_Payment_OrgId_PaymentNumber",
46+
table: "Payments");
47+
48+
// Restore the original (incorrect) single-column index
49+
migrationBuilder.CreateIndex(
50+
name: "IX_Invoices_InvoiceNumber",
51+
table: "Invoices",
52+
column: "InvoiceNumber",
53+
unique: true);
54+
}
55+
}
56+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Aquiis.Infrastructure.Services;
2+
3+
/// <summary>
4+
/// Singleton service tracking database encryption unlock state during app lifecycle
5+
/// </summary>
6+
public class DatabaseUnlockState
7+
{
8+
public bool NeedsUnlock { get; set; }
9+
public string? DatabasePath { get; set; }
10+
public string? ConnectionString { get; set; }
11+
12+
// Event to notify when unlock succeeds
13+
public event Action? OnUnlockSuccess;
14+
15+
public void NotifyUnlockSuccess() => OnUnlockSuccess?.Invoke();
16+
}

2-Aquiis.Application/DependencyInjection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static IServiceCollection AddApplication(
3434
services.AddScoped<DigestService>();
3535
services.AddScoped<DocumentNotificationService>();
3636
services.AddScoped<DocumentService>();
37+
services.AddScoped<DatabasePreviewService>();
3738
services.AddScoped<EmailService>();
3839
services.AddScoped<EmailSettingsService>();
3940
services.AddScoped<FinancialReportService>();
@@ -55,6 +56,7 @@ public static IServiceCollection AddApplication(
5556
services.AddScoped<ProspectiveTenantService>();
5657
services.AddScoped<RentalApplicationService>();
5758
services.AddScoped<RepairService>();
59+
services.AddScoped<SampleDataWorkflowService>();
5860
services.AddScoped<ScheduledTaskService>();
5961
services.AddScoped<SchemaValidationService>();
6062
services.AddScoped<ScreeningService>();
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace Aquiis.Application.Models.DTOs;
2+
3+
/// <summary>
4+
/// DTO for database preview summary data
5+
/// </summary>
6+
public class DatabasePreviewData
7+
{
8+
public int PropertyCount { get; set; }
9+
public int TenantCount { get; set; }
10+
public int LeaseCount { get; set; }
11+
public int InvoiceCount { get; set; }
12+
public int PaymentCount { get; set; }
13+
14+
public List<PropertyPreview> Properties { get; set; } = new();
15+
public List<TenantPreview> Tenants { get; set; } = new();
16+
public List<LeasePreview> Leases { get; set; } = new();
17+
}
18+
19+
/// <summary>
20+
/// DTO for property preview in read-only database view
21+
/// </summary>
22+
public class PropertyPreview
23+
{
24+
public Guid Id { get; set; }
25+
public string Address { get; set; } = string.Empty;
26+
public string City { get; set; } = string.Empty;
27+
public string State { get; set; } = string.Empty;
28+
public string ZipCode { get; set; } = string.Empty;
29+
public string PropertyType { get; set; } = string.Empty;
30+
public string Status { get; set; } = string.Empty;
31+
public int? Units { get; set; }
32+
public decimal? MonthlyRent { get; set; }
33+
}
34+
35+
/// <summary>
36+
/// DTO for tenant preview in read-only database view
37+
/// </summary>
38+
public class TenantPreview
39+
{
40+
public Guid Id { get; set; }
41+
public string FirstName { get; set; } = string.Empty;
42+
public string LastName { get; set; } = string.Empty;
43+
public string FullName => $"{FirstName} {LastName}";
44+
public string Email { get; set; } = string.Empty;
45+
public string Phone { get; set; } = string.Empty;
46+
public DateTime CreatedOn { get; set; }
47+
}
48+
49+
/// <summary>
50+
/// DTO for lease preview in read-only database view
51+
/// </summary>
52+
public class LeasePreview
53+
{
54+
public Guid Id { get; set; }
55+
public string PropertyAddress { get; set; } = string.Empty;
56+
public string TenantName { get; set; } = string.Empty;
57+
public DateTime StartDate { get; set; }
58+
public DateTime EndDate { get; set; }
59+
public decimal MonthlyRent { get; set; }
60+
public string Status { get; set; } = string.Empty;
61+
}
62+
63+
/// <summary>
64+
/// Result object for database operations
65+
/// </summary>
66+
public class DatabaseOperationResult
67+
{
68+
public bool Success { get; set; }
69+
public string Message { get; set; } = string.Empty;
70+
71+
public static DatabaseOperationResult SuccessResult(string message = "Operation successful")
72+
=> new() { Success = true, Message = message };
73+
74+
public static DatabaseOperationResult FailureResult(string message)
75+
=> new() { Success = false, Message = message };
76+
}

0 commit comments

Comments
 (0)