Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ services:
- Minio__AccessKey=${MINIO_ACCESS_KEY}
- Minio__SecretKey=${MINIO_SECRET_KEY}
- Minio__BucketName=${MINIO_BUCKET}
- MaxMind__AccountId=${MAXMIND_ACCOUNT_ID}
- MaxMind__LicenseKey=${MAXMIND_LICENSE_KEY}

ports:
- "5005:80"
Expand Down
5 changes: 3 additions & 2 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<LangVersion>default</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="Ardalis.Specification" Version="9.3.1" />
Expand All @@ -18,8 +19,8 @@
<PackageReference Include="MimeKit" Version="4.14.0" />
<PackageReference Include="Scriban" Version="6.5.0" />

<PackageReference Include="FluentValidation" Version="12.0.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
Expand Down
87 changes: 0 additions & 87 deletions src/Application/Common/Interfaces/IGeolocationService.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
Expand All @@ -12,59 +12,71 @@
using CleanArchitecture.Blazor.Domain.Enums;
using CleanArchitecture.Blazor.Domain.Identity;
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
using MaxMind.GeoIP2;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;

namespace CleanArchitecture.Blazor.Application.Features.LoginAudits.EventHandlers;

public class LoginAuditCreatedEventHandler : INotificationHandler<LoginAuditCreatedEvent>
{
private readonly IGeolocationService _geolocationService;

private readonly IServiceScopeFactory _scopeFactory;

private readonly ILogger<LoginAuditCreatedEventHandler> _logger;
private readonly ISecurityAnalysisService _securityAnalysisService;

public LoginAuditCreatedEventHandler(
IGeolocationService geolocationService,
IServiceScopeFactory scopeFactory,
ILogger<LoginAuditCreatedEventHandler> logger,
ISecurityAnalysisService securityAnalysisService)
{
_geolocationService = geolocationService;
_scopeFactory = scopeFactory;
_logger = logger;
_securityAnalysisService = securityAnalysisService;
}

public async Task Handle(LoginAuditCreatedEvent notification, CancellationToken cancellationToken)
{
notification.Item.IpAddress = "186.150.138.239";
if (!string.IsNullOrEmpty(notification.Item.IpAddress) && !notification.Item.IpAddress.StartsWith("127") && string.IsNullOrEmpty(notification.Item.Region))
{
var geolocation = await _geolocationService.GetGeolocationAsync(notification.Item.IpAddress, cancellationToken);
if (geolocation != null)
try
{
var regionParts = new List<string>();
using (var scope = _scopeFactory.CreateScope())
{
using (var client = scope.ServiceProvider.GetRequiredService<WebServiceClient>())
{
var geolocation = await client.CityAsync(notification.Item.IpAddress);
if (geolocation != null)
{
var regionParts = new List<string>();

if (!string.IsNullOrEmpty(geolocation.City))
regionParts.Add(geolocation.City);
if (!string.IsNullOrEmpty(geolocation.City.Name))
regionParts.Add(geolocation.City.Name);

if (!string.IsNullOrEmpty(geolocation.Region))
regionParts.Add(geolocation.Region);
if (geolocation.Subdivisions.Any())
regionParts.Add(geolocation.Subdivisions.FirstOrDefault().Name);

if (!string.IsNullOrEmpty(geolocation.CountryName))
regionParts.Add(geolocation.CountryName);
else if (!string.IsNullOrEmpty(geolocation.Country))
regionParts.Add(geolocation.Country);
if (!string.IsNullOrEmpty(geolocation.Country.Name))
regionParts.Add(geolocation.Country.Name);

var region = regionParts.Count > 0 ? string.Join(", ", regionParts) : null;
using (var scope = _scopeFactory.CreateScope())
{
var dbContextFactory = scope.ServiceProvider.GetRequiredService<IApplicationDbContextFactory>();
await using var db = await dbContextFactory.CreateAsync(cancellationToken);
await db.LoginAudits.Where(x => x.Id == notification.Item.Id).ExecuteUpdateAsync(x => x.SetProperty(y => y.Region, region));

var region = regionParts.Count > 0 ? string.Join(", ", regionParts) : null;


var dbContextFactory = scope.ServiceProvider.GetRequiredService<IApplicationDbContextFactory>();
await using var db = await dbContextFactory.CreateAsync(cancellationToken);
await db.LoginAudits.Where(x => x.Id == notification.Item.Id).ExecuteUpdateAsync(x => x.SetProperty(y => y.Region, region));

}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get geolocation for IP: {IpAddress}", notification.Item.IpAddress);
}
}

// Analyze account security using the dedicated service in a new scope
Expand Down
37 changes: 19 additions & 18 deletions src/Infrastructure/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using CleanArchitecture.Blazor.Application.Common.Constants;
using CleanArchitecture.Blazor.Application.Common.Interfaces; // IDataSourceService
using CleanArchitecture.Blazor.Application.Common.Models;
using CleanArchitecture.Blazor.Application.Common.Security;
using CleanArchitecture.Blazor.Application.Features.Identity.DTOs;
using CleanArchitecture.Blazor.Application.Features.PicklistSets.DTOs;
using CleanArchitecture.Blazor.Application.Features.Tenants.DTOs;
using CleanArchitecture.Blazor.Domain.Identity;
using CleanArchitecture.Blazor.Infrastructure.Configurations;
using CleanArchitecture.Blazor.Application.Common.Security;
using CleanArchitecture.Blazor.Infrastructure.Persistence.Interceptors;
using CleanArchitecture.Blazor.Infrastructure.Services;
using CleanArchitecture.Blazor.Infrastructure.Services.Circuits;
using CleanArchitecture.Blazor.Infrastructure.Services.Gemini;
using CleanArchitecture.Blazor.Infrastructure.Services.Identity;
using CleanArchitecture.Blazor.Infrastructure.Services.MultiTenant;
using MaxMind.GeoIP2;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using ZiggyCreatures.Caching.Fusion;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Http;
using CleanArchitecture.Blazor.Application.Common.Constants;
using CleanArchitecture.Blazor.Infrastructure.Services.Identity;
using CleanArchitecture.Blazor.Infrastructure.Services;
using CleanArchitecture.Blazor.Application.Common.Interfaces; // IDataSourceService
using CleanArchitecture.Blazor.Application.Features.Tenants.DTOs;
using CleanArchitecture.Blazor.Application.Features.Identity.DTOs;
using CleanArchitecture.Blazor.Application.Features.PicklistSets.DTOs;

namespace CleanArchitecture.Blazor.Infrastructure;

Expand Down Expand Up @@ -80,6 +81,8 @@ private static IServiceCollection AddApplicationSettings(this IServiceCollection
services.Configure<AISettings>(configuration.GetSection(AISettings.Key))
.AddSingleton(s => s.GetRequiredService<IOptions<AISettings>>().Value)
.AddSingleton<IAISettings>(s => s.GetRequiredService<IOptions<AISettings>>().Value);


return services;
}
#endregion
Expand Down Expand Up @@ -171,17 +174,15 @@ private static IServiceCollection AddBusinessServices(this IServiceCollection se
services.AddScoped<ITenantSwitchService, TenantSwitchService>();


// Configure HttpClient for GeolocationService
services.AddHttpClient<IGeolocationService, GeolocationService>(client =>
{
client.Timeout = TimeSpan.FromSeconds(10);
client.DefaultRequestHeaders.Add("User-Agent", "CleanArchitectureBlazorServer/1.0");
});


// Configure SecurityAnalysisService with options
services.Configure<WebServiceClientOptions>(configuration.GetSection("MaxMind"));
services.AddHttpClient<WebServiceClient>();

services.Configure<SecurityAnalysisOptions>(configuration.GetSection(SecurityAnalysisOptions.SectionName));
services.AddScoped<ISecurityAnalysisService, SecurityAnalysisService>();

return services
.AddScoped<IValidationService, ValidationService>()
.AddScoped<IDateTime, DateTimeService>()
Expand Down Expand Up @@ -329,7 +330,7 @@ private static IServiceCollection AddIdentityAndSecurity(this IServiceCollection
});
services.AddDataProtection().PersistKeysToDbContext<ApplicationDbContext>();



return services;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Infrastructure/Infrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
<LangVersion>default</LangVersion>
</PropertyGroup>
<ItemGroup>

<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="9.0.10" />
<PackageReference Include="Minio" Version="6.0.5" />
<PackageReference Include="QuestPDF" Version="2025.7.3" />
<PackageReference Include="QuestPDF" Version="2025.7.4" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="9.0.1" />
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.Postgresql.Alternative" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Blazor.Serilog.Sinks.SQLite" Version="1.0.8" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
</ItemGroup>
Expand Down
Loading
Loading