CG.Infrastructure.Authentication 3.10.2

dotnet add package CG.Infrastructure.Authentication --version 3.10.2
                    
NuGet\Install-Package CG.Infrastructure.Authentication -Version 3.10.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CG.Infrastructure.Authentication" Version="3.10.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CG.Infrastructure.Authentication" Version="3.10.2" />
                    
Directory.Packages.props
<PackageReference Include="CG.Infrastructure.Authentication" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CG.Infrastructure.Authentication --version 3.10.2
                    
#r "nuget: CG.Infrastructure.Authentication, 3.10.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CG.Infrastructure.Authentication@3.10.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CG.Infrastructure.Authentication&version=3.10.2
                    
Install as a Cake Addin
#tool nuget:?package=CG.Infrastructure.Authentication&version=3.10.2
                    
Install as a Cake Tool

Infrastructure.Authentication

A .NET 9.0 library that provides comprehensive ASP.NET Core Identity infrastructure with custom repository pattern implementation, designed for enterprise applications requiring flexible authentication and authorization systems.

๐Ÿš€ Overview

Infrastructure.Authentication is a modern, flexible authentication library that extends ASP.NET Core Identity with custom data access patterns. It provides a clean separation between business logic and data persistence, supporting both traditional Entity Framework and custom repository implementations.

๐Ÿ† Current Status

โœ… Production Ready: All 242 tests passing with comprehensive coverage
โœ… Code Quality: Zero warnings, clean build, comprehensive null safety
โœ… Modern Patterns: Latest .NET 9.0 features with best practices
โœ… Enterprise Grade: Robust error handling and transaction management
โœ… Well Tested: Extensive unit test coverage with clean mocking patterns

โœจ Features

  • ASP.NET Core Identity Integration: Full compatibility with Microsoft's Identity framework
  • Custom Repository Pattern: Flexible data access layer supporting multiple data sources
  • Query Constants Pattern: SQL queries defined as C# constants for better maintainability
  • Transaction Support: Comprehensive transaction management for complex operations
  • Null Safety: Built-in null reference handling and validation
  • Clean Architecture: Separation of concerns with clear dependency boundaries
  • Extensible Design: Easy to extend and customize for specific business requirements

๐Ÿ—๏ธ Architecture

Core Components

Infrastructure.Authentication/
โ”œโ”€โ”€ Data/
โ”‚   โ”œโ”€โ”€ Entities/                    # Identity entities (ApplicationUser, ApplicationRole, etc.)
โ”‚   โ”‚   โ”œโ”€โ”€ ApplicationUser.cs       # Main user entity
โ”‚   โ”‚   โ”œโ”€โ”€ ApplicationRole.cs       # Main role entity
โ”‚   โ”‚   โ”œโ”€โ”€ UserClaim.cs             # User claim entity
โ”‚   โ”‚   โ”œโ”€โ”€ RoleClaim.cs             # Role claim entity
โ”‚   โ”‚   โ”œโ”€โ”€ UserLogin.cs             # External login entity
โ”‚   โ”‚   โ”œโ”€โ”€ UserRole.cs              # User-role relationship entity
โ”‚   โ”‚   โ”œโ”€โ”€ UserToken.cs             # User token entity
โ”‚   โ”‚   โ”œโ”€โ”€ TestUsers.cs             # Test data for development
โ”‚   โ”‚   โ””โ”€โ”€ UserJson.cs              # JSON user data entity
โ”‚   โ”œโ”€โ”€ Repositories/                # Data access layer
โ”‚   โ”‚   โ”œโ”€โ”€ UserRepository.cs        # User data operations (includes claims, logins, roles, tokens)
โ”‚   โ”‚   โ””โ”€โ”€ RoleRepository.cs        # Role data operations (includes role claims)
โ”œโ”€โ”€ Providers/                       # Business logic providers
โ”‚   โ”œโ”€โ”€ UserProvider.cs              # User management operations
โ”‚   โ”œโ”€โ”€ RoleProvider.cs              # Role management operations
โ”‚   โ”œโ”€โ”€ UserClaimProvider.cs         # User claim operations
โ”‚   โ”œโ”€โ”€ RoleClaimProvider.cs         # Role claim operations
โ”‚   โ”œโ”€โ”€ UserLoginProvider.cs         # External login operations
โ”‚   โ”œโ”€โ”€ UserRoleProvider.cs          # User-role relationship operations
โ”‚   โ””โ”€โ”€ UserTokenProvider.cs         # User token operations
โ”œโ”€โ”€ Services/                        # Application services
โ”‚   โ””โ”€โ”€ AuthenticationDatabaseService.cs  # Database seeding and setup
โ”œโ”€โ”€ Options/                         # Configuration options
โ”‚   โ””โ”€โ”€ AuthDatabaseOptions.cs       # Authentication database settings
โ”œโ”€โ”€ Interfaces/                      # Service and repository contracts
โ”‚   โ”œโ”€โ”€ IUserRepository.cs           # User repository interface (handles all user-related data)
โ”‚   โ”œโ”€โ”€ IRoleRepository.cs           # Role repository interface (handles all role-related data)
โ”‚   โ”œโ”€โ”€ IUserProvider.cs             # User provider interface
โ”‚   โ”œโ”€โ”€ IRoleProvider.cs             # Role provider interface
โ”‚   โ”œโ”€โ”€ IUserClaimProvider.cs        # User claim provider interface
โ”‚   โ”œโ”€โ”€ IRoleClaimProvider.cs        # Role claim provider interface
โ”‚   โ”œโ”€โ”€ IUserLoginProvider.cs        # User login provider interface
โ”‚   โ”œโ”€โ”€ IUserRoleProvider.cs         # User role provider interface
โ”‚   โ”œโ”€โ”€ IUserTokenProvider.cs        # User token provider interface
โ”‚   โ””โ”€โ”€ IAuthenticationDatabaseService.cs  # Database service interface
โ””โ”€โ”€ Extensions/                      # Service collection extensions
    โ”œโ”€โ”€ ServiceCollectionExtensions.cs      # DI container configuration
    โ””โ”€โ”€ IdentityBuilderExtensions.cs        # Identity builder configuration

Data Flow

  1. Controllers/Handlers โ†’ Providers โ†’ Repositories โ†’ Database
  2. Providers handle business logic and orchestrate repository operations
  3. Repositories execute SQL queries using query constants with automatic transaction management
  4. Query Constants provide maintainable SQL definitions

Note: The library uses a consolidated repository pattern where:

  • UserRepository handles all user-related operations (users, claims, logins, roles, tokens)
  • RoleRepository handles all role-related operations (roles, role claims)
  • This design provides better transaction management and reduces complexity compared to separate repositories for each entity type

Transaction Management

  • Automatic Transactions: Each transaction method manages its own transaction lifecycle
  • Resource Safety: Using using var transaction ensures proper disposal
  • Exception Handling: Automatic rollback on errors with clean exception propagation
  • No Shared State: Each method is completely self-contained

๐ŸŽฏ Key Benefits

1. Query Constants Pattern

Instead of stored procedures or embedded SQL files, queries are defined as C# constants:

public static class AspNetIdentityQueries
{
    public const string Users_GetAll = @"
        SELECT [Id], [UserName], [NormalizedUserName], [Email], [NormalizedEmail], 
               [EmailConfirmed], [PasswordHash], [SecurityStamp], [ConcurrencyStamp], 
               [PhoneNumber], [PhoneNumberConfirmed], [TwoFactorEnabled], [LockoutEnd], 
               [LockoutEnabled], [AccessFailedCount]
        FROM [dbo].[AspNetUsers]
        ORDER BY [UserName]";
}

Advantages:

  • Version Control: Queries are tracked in source control
  • Type Safety: Compile-time validation of SQL syntax
  • Maintainability: Easy to refactor and update queries
  • Performance: No runtime file loading or parsing overhead

2. Repository Pattern

Clean abstraction layer between business logic and data access using a consolidated approach:

public class UserRepository : IUserRepository
{
    // User operations
    public async Task<ApplicationUser?> GetApplicationUserById(string? userId)
    {
        return await dataAccess.LoadFirst<ApplicationUser, dynamic>(
            AspNetIdentityQueries.Users_GetById,
            new { Id = userId },
            commandType: CommandType.Text,
            connection.ConnectionName!);
    }
    
    // User claim operations
    public async Task<Guid> CreateUserClaimInTransaction(UserClaim entity)
    {
        using var transaction = dataAccess.StartTransaction(connection.ConnectionName!);
        try
        {
            await dataAccess.SaveDataInTransaction(AspNetIdentityQueries.UserClaims_Insert,
                                                    entity,
                                                    transaction,
                                                    CommandType.Text);
            dataAccess.CommitTransaction(transaction);
            return entity.Id;
        }
        catch
        {
            dataAccess.RollbackTransaction(transaction);
            throw;
        }
    }
    
    // User login, role, and token operations are also handled here
    // This consolidated approach provides better transaction management
}

Benefits of Consolidated Repositories:

  • Simplified Transaction Management: All related operations can be wrapped in single transactions
  • Reduced Complexity: Fewer repository classes to maintain and inject
  • Better Performance: Fewer database connections and transaction overhead
  • Consistent Patterns: Same data access patterns across all user-related operations

3. Modern Transaction Pattern

Automatic transaction management using the using pattern for better resource management:

public async Task<Guid> CreateUserClaimInTransaction(UserClaim entity)
{
    using var transaction = dataAccess.StartTransaction(connection.ConnectionName!);
    try
    {
        await dataAccess.SaveDataInTransaction(AspNetIdentityQueries.UserClaims_Insert,
                                                entity,
                                                transaction,
                                                CommandType.Text);

        dataAccess.CommitTransaction(transaction);
        return entity.Id;
    }
    catch
    {
        dataAccess.RollbackTransaction(transaction);
        throw;
    }
}

Benefits:

  • Automatic Resource Management: Transactions are disposed automatically
  • Exception Safety: Automatic rollback on errors
  • Cleaner Code: No manual transaction state management
  • Consistent Pattern: Same approach used across all repositories

4. Identity Store Integration

Custom ASP.NET Core Identity stores for flexible data access:

// Custom UserStore implementation
public class UserStore : IUserStore<ApplicationUser>, IUserEmailStore<ApplicationUser>
{
    private readonly IUserRepository _userRepository;
    
    public async Task<ApplicationUser?> FindByIdAsync(string userId, CancellationToken cancellationToken)
    {
        return await _userRepository.GetApplicationUserById(userId);
    }
    
    // ... other Identity store methods
}

5. Updated Provider Pattern

Providers now work seamlessly with the new transaction pattern:

// RoleProvider automatically uses the new transaction pattern
public async Task<IdentityResult> UpdateApplicationRole(ApplicationRole role)
{
    try
    {
        await _roleRepository.UpdateApplicationRoleInTransaction(role);
        // Claims are automatically handled in transactions
        // No manual transaction management needed
        return IdentityResult.Success;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed updating application role");
        return IdentityResult.Failed(new IdentityError { ... });
    }
}

6. Extension Methods

Easy service configuration and Identity setup:

// In Program.cs or Startup.cs
services.AddInfrastructureAuthentication(options =>
{
    options.ConnectionString = configuration.GetConnectionString("DefaultConnection");
    options.DbSchema = "dbo";
});

// Configure Identity with custom stores
services.AddDefaultIdentity<ApplicationUser>()
    .AddRoles<ApplicationRole>()
    .AddInfrastructureStores();

๐Ÿ”„ Recent Improvements & Bug Fixes

AuthenticationDatabaseService Enhancements

  • File System Abstraction: Replaced File.Exists() with IDocumentService.FileExists() for better testability
  • Null Path Handling: Added proper null checking for file paths to prevent runtime errors
  • Bug Fix: Fixed GetUser method returning null instead of existing user when user already exists

Provider Pattern Improvements

  • Exception Safety: All providers now throw ArgumentNullException instead of NullReferenceException
  • Consistent Validation: Standardized null checking across all provider methods
  • Better Error Messages: Clear, actionable error messages for debugging

Test Infrastructure

  • Moq Setup: Resolved all Moq setup issues with proper mocking patterns
  • Configuration Testing: Real configuration objects instead of complex mocking for better test reliability
  • Interface Validation: Comprehensive testing of interface contracts and method signatures

๐Ÿ”„ Migration from Stored Procedures

This library has been migrated from a stored procedure-based approach to a modern query constants pattern. This migration provides several benefits:

What Changed

  • Before: 31 stored procedures for ASP.NET Core Identity operations
  • After: SQL queries defined as C# constants in AspNetIdentityQueries.cs
  • Transaction Management: Moved from manual BeginTransaction/CommitTransaction/RollbackTransaction to automatic using var transaction pattern

Migration Benefits

  • Better Version Control: SQL queries are now tracked in source control
  • Easier Testing: Queries can be unit tested independently
  • Improved Performance: No stored procedure compilation overhead
  • Better Maintainability: Queries are co-located with repository code
  • Type Safety: Compile-time validation of SQL syntax

Example Migration

Before (Stored Procedures)
// Old approach using stored procedures
return await dataAccess.LoadData<ApplicationUser, dynamic>(
    $"{_options.DbSchema}.sp_AspNetUsers_GetAll",
    new { },
    commandType: CommandType.StoredProcedure,
    _connection.ConnectionName);
After (Query Constants)
// New approach using query constants
return await dataAccess.LoadData<ApplicationUser, dynamic>(
    AspNetIdentityQueries.Users_GetAll,
    new { },
    commandType: CommandType.Text,
    connection.ConnectionName!);

Migration Benefits

  • Eliminates stored procedure maintenance
  • Better version control and deployment
  • Easier testing and debugging
  • Consistent with modern development practices

๐Ÿ”ง Installation & Setup

Package Reference

<PackageReference Include="CG.Infrastructure.Authentication" Version="3.10.0" />

Service Registration

// Program.cs or Startup.cs
// Repository registrations
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();

// Provider registrations
services.AddScoped<IUserProvider, UserProvider>();
services.AddScoped<IRoleProvider, RoleProvider>();
services.AddScoped<IUserClaimProvider, UserClaimProvider>();
services.AddScoped<IRoleClaimProvider, RoleClaimProvider>();
services.AddScoped<IUserLoginProvider, UserLoginProvider>();
services.AddScoped<IUserRoleProvider, UserRoleProvider>();
services.AddScoped<IUserTokenProvider, UserTokenProvider>();

// Service registrations
services.AddScoped<IAuthenticationDatabaseService, AuthenticationDatabaseService>();

// Identity store registrations
services.AddScoped<IUserStore<ApplicationUser>, UserStore>();
services.AddScoped<IRoleStore<ApplicationRole>, RoleStore>();

Configuration

services.Configure<AuthDatabaseOptions>(configuration.GetSection("ConfigOptions"));
services.Configure<ConnectionOptions>(configuration.GetSection("ConnectionStrings"));

๐Ÿ“– Usage Examples

User Management

public class UserController : ControllerBase
{
    private readonly IUserProvider _userProvider;
    
    public UserController(IUserProvider userProvider)
    {
        _userProvider = userProvider;
    }
    
    [HttpGet]
    public async Task<ActionResult<IEnumerable<ApplicationUser>>> GetUsers()
    {
        var users = await _userProvider.GetAllApplicationUsers();
        return Ok(users);
    }
    
    [HttpPost]
    public async Task<ActionResult> CreateUser([FromBody] ApplicationUser user)
    {
        var result = await _userProvider.CreateApplicationUser(user);
        
        if (result.Succeeded)
            return CreatedAtAction(nameof(GetUsers), user);
            
        return BadRequest(result.Errors);
    }
}

Role Management

public class RoleController : ControllerBase
{
    private readonly IRoleProvider _roleProvider;
    
    public RoleController(IRoleProvider roleProvider)
    {
        _roleProvider = roleProvider;
    }
    
    [HttpGet]
    public async Task<ActionResult<IEnumerable<ApplicationRole>>> GetRoles()
    {
        var roles = await _roleProvider.GetAllApplicationRoles();
        return Ok(roles);
    }
}

Custom Queries

// Add custom queries to AspNetIdentityQueries.cs
public const string Users_GetByDepartment = @"
    SELECT u.* FROM [dbo].[AspNetUsers] u
    INNER JOIN [dbo].[UserDepartments] ud ON u.Id = ud.UserId
    WHERE ud.DepartmentId = @DepartmentId";

// Use in repository
public async Task<IReadOnlyList<ApplicationUser>> GetUsersByDepartment(int departmentId)
{
    return await dataAccess.LoadData<ApplicationUser, dynamic>(
        AspNetIdentityQueries.Users_GetByDepartment,
        new { DepartmentId = departmentId },
        connection.ConnectionName!,
        CommandType.Text);
}

Transaction Operations with New Pattern

// Create user claim with automatic transaction management
var userClaim = new UserClaim
{
    ClaimType = "Permission",
    ClaimValue = "Read",
    UserId = "user123"
};

var claimId = await userRepository.CreateUserClaimInTransaction(userClaim);
// Transaction is automatically managed - no manual Begin/Commit/Rollback needed

// Update user with claims, logins, and roles
var result = await userProvider.UpdateApplicationUser(user);
// All operations are automatically wrapped in transactions by the repository methods

๐Ÿ—„๏ธ Database Schema

The library works with standard ASP.NET Core Identity tables:

  • AspNetUsers: User accounts and authentication data
  • AspNetRoles: Application roles
  • AspNetUserRoles: User-role relationships
  • AspNetUserClaims: User-specific claims
  • AspNetRoleClaims: Role-specific claims
  • AspNetUserLogins: External login providers
  • AspNetUserTokens: User authentication tokens

๐Ÿงช Testing

Current Test Status

โœ… All 242 tests are passing with comprehensive coverage of:

  • Interface contract validation
  • Provider business logic
  • Repository data access patterns
  • Service orchestration
  • Configuration and dependency injection

Code Quality Improvements

  • CA2263 Warnings Resolved: All code analysis warnings have been addressed
  • Null Safety: Comprehensive null reference handling throughout the codebase
  • Moq Setup: Clean, maintainable test mocking patterns
  • Exception Handling: Proper ArgumentNullException throwing instead of NullReferenceException

Unit Testing Repositories

[Fact]
public async Task GetApplicationUserById_WithValidId_ReturnsUser()
{
    // Arrange
    var mockDataAccess = new Mock<IDataAccess>();
    var mockConnection = new Mock<ConnectionOptions>();
    var repository = new UserRepository(mockDataAccess.Object, mockConnection.Object);
    
    var expectedUser = new ApplicationUser { Id = "test-id", UserName = "testuser" };
    mockDataAccess.Setup(x => x.LoadFirst<ApplicationUser, dynamic>(
        AspNetIdentityQueries.Users_GetById,
        It.IsAny<dynamic>(),
        CommandType.Text,
        It.IsAny<string>()))
        .ReturnsAsync(expectedUser);
    
    // Act
    var result = await repository.GetApplicationUserById("test-id");
    
    // Assert
    Assert.Equal(expectedUser, result);
}

Integration Testing

[Fact]
public async Task CreateApplicationUser_WithValidData_SavesToDatabase()
{
    // Arrange
    var user = new ApplicationUser 
    { 
        Id = "test-id", 
        UserName = "testuser",
        Email = "test@example.com" 
    };
    
    // Act
    var result = await _userProvider.CreateApplicationUser(user);
    
    // Assert
    Assert.True(result.Succeeded);
    var savedUser = await _userProvider.GetApplicationUserById(Guid.Parse(user.Id));
    Assert.NotNull(savedUser);
}

๐Ÿšจ Error Handling

Null Safety

The library includes comprehensive null safety with proper exception handling:

// Null coalescing for claim values
return [.. claims.Select(s => new Claim(
    s.ClaimType ?? string.Empty, 
    s.ClaimValue ?? string.Empty))];

// Null checks before operations with proper exceptions
ArgumentNullException.ThrowIfNull(user);
if (user != null && entity.Role != null)
{
    await CreateRole(entity.Role);
    await AddUserToRole(user, entity.Role);
}

// File path null safety
if (string.IsNullOrEmpty(_usersPath) || !_documentService.FileExists(_usersPath))
{
    // Handle case where no file path is configured
}

Transaction Rollback

try
{
    _userRepository.BeginTransaction();
    // ... operations
    _userRepository.CommitTransaction();
}
catch (Exception ex)
{
    _userRepository.RollbackTransaction();
    _logger.LogError(ex, "Operation failed and was rolled back");
    throw;
}

๐Ÿ”’ Security Considerations

  • Parameterized Queries: All SQL queries use parameterized inputs to prevent SQL injection
  • Input Validation: Comprehensive validation of user inputs
  • Transaction Isolation: Proper transaction isolation levels for concurrent operations
  • Audit Trail: Built-in logging for security-related operations

๐Ÿ“š Dependencies

Core Framework

  • .NET 9.0: Target framework

CG Infrastructure Libraries

  • CG.Infrastructure.Configuration (3.9.2): Configuration management and options
  • CG.Infrastructure.Core (3.9.0): Core infrastructure components
  • CG.Infrastructure.Data (3.9.6): Data access and persistence abstractions
  • CG.Infrastructure.Entity (3.10.1): Entity framework and data modeling
  • CG.Infrastructure.Services (3.9.1): Service layer infrastructure

Identity & Authentication

  • Microsoft.Extensions.Identity.Core (9.0.2): ASP.NET Core Identity core functionality
  • Microsoft.Extensions.Identity.Stores (9.0.2): Identity data stores and persistence
  • Duende.IdentityServer (7.1.0): OpenID Connect and OAuth 2.0 server
  • Duende.IdentityServer.AspNetIdentity (7.1.0): IdentityServer integration with ASP.NET Core Identity

Utilities

  • AutoMapper (14.0.0): Object-to-object mapping library

๐Ÿค Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Guidelines

  • Follow C# coding conventions
  • Add unit tests for new functionality
  • Update documentation for API changes
  • Ensure null safety in all public methods
  • Use query constants for all SQL operations

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ†˜ Support

  • Documentation: This README and inline code comments
  • Issues: Create an issue in the project repository
  • Discussions: Use GitHub Discussions for questions and ideas
  • Email: Contact the development team directly

๐Ÿ”ฎ Roadmap

  • Code Quality: All CA2263 warnings resolved
  • Test Coverage: 242/242 tests passing
  • Null Safety: Comprehensive null reference handling implemented
  • Exception Handling: Proper ArgumentNullException patterns
  • Performance Optimization: Query performance analysis and optimization
  • Caching Layer: Redis integration for frequently accessed data
  • Audit Logging: Comprehensive audit trail for all operations
  • Multi-tenancy: Support for multi-tenant applications
  • GraphQL Support: GraphQL endpoint for flexible data querying
  • Event Sourcing: Event-driven architecture for complex workflows

Built with โค๏ธ by the CG Infrastructure Team

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CG.Infrastructure.Authentication:

Package Downloads
CG.Infrastructure.Identity

Infra Identity library with Duende setup, extensions and database contexts

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.10.2 132 8/21/2025
3.10.1 127 8/20/2025
3.9.2 127 2/26/2025
3.9.1 129 2/25/2025
3.9.0 121 2/20/2025