Implemented all 3 critical fixes identified in Day 6 Architecture Gap Analysis:
**Fix 1: UpdateUserRole Feature (RESTful PUT endpoint)**
- Created UpdateUserRoleCommand and UpdateUserRoleCommandHandler
- Added PUT /api/tenants/{tenantId}/users/{userId}/role endpoint
- Implements self-demotion prevention (cannot demote self from TenantOwner)
- Implements last owner protection (cannot remove last TenantOwner)
- Returns UserWithRoleDto with updated role information
- Follows RESTful best practices (PUT for updates)
**Fix 2: Last TenantOwner Deletion Prevention (Security)**
- Verified CountByTenantAndRoleAsync repository method exists
- Verified IsLastTenantOwnerAsync validation in RemoveUserFromTenantCommandHandler
- UpdateUserRoleCommandHandler now prevents:
* Self-demotion from TenantOwner role
* Removing the last TenantOwner from tenant
- SECURITY: Prevents tenant from becoming ownerless (critical vulnerability fix)
**Fix 3: Database-Backed Rate Limiting (Security & Reliability)**
- Created EmailRateLimit entity with proper domain logic
- Added EmailRateLimitConfiguration for EF Core
- Implemented DatabaseEmailRateLimiter service (replaces MemoryRateLimitService)
- Updated DependencyInjection to use database-backed implementation
- Created database migration: AddEmailRateLimitsTable
- Added composite unique index on (email, tenant_id, operation_type)
- SECURITY: Rate limit state persists across server restarts (prevents email bombing)
- Implements cleanup logic for expired rate limit records
**Testing:**
- Added 9 comprehensive integration tests in Day8GapFixesTests.cs
- Fix 1: 3 tests (valid update, self-demote prevention, idempotency)
- Fix 2: 3 tests (remove last owner fails, update last owner fails, remove 2nd-to-last succeeds)
- Fix 3: 3 tests (persists across requests, expiry after window, prevents bulk emails)
- 6 tests passing, 3 skipped (long-running/environment-specific tests)
**Files Changed:**
- 6 new files created
- 6 existing files modified
- 1 database migration added
- All existing tests still pass (no regressions)
**Verification:**
- Build succeeds with no errors
- All critical business logic tests pass
- Database migration generated successfully
- Security vulnerabilities addressed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
54 lines
2.0 KiB
C#
54 lines
2.0 KiB
C#
using System;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
#nullable disable
|
|
|
|
namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations
|
|
{
|
|
/// <inheritdoc />
|
|
public partial class AddEmailRateLimitsTable : Migration
|
|
{
|
|
/// <inheritdoc />
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.CreateTable(
|
|
name: "email_rate_limits",
|
|
schema: "identity",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
email = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
|
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
operation_type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
|
last_sent_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
|
attempts_count = table.Column<int>(type: "integer", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_email_rate_limits", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "ix_email_rate_limits_email_tenant_operation",
|
|
schema: "identity",
|
|
table: "email_rate_limits",
|
|
columns: new[] { "email", "tenant_id", "operation_type" },
|
|
unique: true);
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "ix_email_rate_limits_last_sent_at",
|
|
schema: "identity",
|
|
table: "email_rate_limits",
|
|
column: "last_sent_at");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.DropTable(
|
|
name: "email_rate_limits",
|
|
schema: "identity");
|
|
}
|
|
}
|
|
}
|