using System.Text; using BlueWest.Cryptography; using BlueWest.Data.Application.Users; using BlueWest.Data.Auth; using BlueWest.Data.Auth.Context.Users; using CodeLiturgy.Domain; using BlueWest.WebApi.Context.Users; using CodeLiturgy.Views.Utils; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Redis.OM; namespace CodeLiturgy.Views; public static class StartupExtensions { private static MySqlServerVersion GetMySqlServerVersion(int major, int minor, int build) => new(new Version(major, minor, build)); private static string GetConnectionString(this IConfiguration configurationRoot, string db) { var startupMode = configurationRoot["mode"]; if (!string.IsNullOrEmpty(startupMode) && startupMode == "docker") { var config = configurationRoot.GetSection("ConnectionStringDocker")[db]; return config; } else { return configurationRoot.GetSection("ConnectionStringNoDocker")[db] ?? string.Empty; } } private static bool IsDockerMode(this IConfiguration config) { var startupMode = config["mode"]; return startupMode == "docker"; } internal static IServiceCollection AddAuthServerServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment) { var connectionString = configuration.GetConnectionString("Redis"); if (string.IsNullOrEmpty(connectionString)) { throw new InvalidOperationException("Redis connection string is empty"); } services.AddSession(options => { options.Cookie.Domain = SessionConstants.CookieDomain; options.Cookie.HttpOnly = true; options.IdleTimeout = TimeSpan.FromHours(8); }); services .AddSingleton(new RedisConnectionProvider(connectionString)) .AddScoped() .AddScoped() .AddHostedService() .AddSingleton() .AddScoped() .AddScoped() .AddScoped() .AddScoped(); // Database Context and Swagger // Register the ConfigurationBuilder instance of AuthSettings var authSettings = configuration.GetSection(nameof(AuthSettings)); services.Configure(authSettings); var signingKey = new SymmetricSecurityKey (Encoding.ASCII.GetBytes(authSettings[nameof(AuthSettings.SecretKey)])); // jwt wire up // Get options from app settings var jwtAppSettingOptions = configuration .GetSection(nameof(JwtIssuerOptions)); // Configure JwtIssuerOptions services.Configure(options => { options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)]; options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)]; options.SigningCredentials = new SigningCredentials (signingKey, SecurityAlgorithms.HmacSha256); }); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)], ValidateAudience = true, ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)], ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, RequireExpirationTime = false, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddCookie(options => { options.LoginPath = Routes.AuthLoginRoute; options.LogoutPath = Routes.AuthLogoutRoute; }); // api user claim policy services.AddAuthorization(options => { options.AddPolicy(SessionConstants.ApiNamePolicy, policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, Constants.JwtClaims.ApiAccess)); }); // add identity var identityBuilder = services.AddIdentityCore(o => { o.User.RequireUniqueEmail = true; // configure identity options o.Password.RequireDigit = false; o.Password.RequireLowercase = false; o.Password.RequireUppercase = false; o.Password.RequireNonAlphanumeric = false; o.Password.RequiredLength = 6; }) .AddUserManager() .AddUserStore(); identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(ApplicationRole), identityBuilder.Services); identityBuilder .AddEntityFrameworkStores() .AddDefaultTokenProviders(); return services; } /// /// Get MYSQL Connection String /// /// /// /// private static DbContextOptionsBuilder GetMySqlSettings( this DbContextOptionsBuilder optionsBuilder, IConfiguration configuration, IWebHostEnvironment environment) { var sqlVersion = GetMySqlServerVersion(8, 0, 11); // Docker / No-Docker var mySqlConnectionString = configuration.GetConnectionString("MySQL"); if (mySqlConnectionString == string.Empty) { throw new InvalidOperationException("MySQL Connection string appears to be empty."); } optionsBuilder .UseMySql( mySqlConnectionString, sqlVersion) .UseMySql(sqlVersion, builder => { builder.EnableRetryOnFailure(6, TimeSpan.FromSeconds(3), null); }); // The following three options help with debugging, but should // be changed or removed for production. if (environment.IsDevelopment()) { optionsBuilder .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging() .EnableDetailedErrors(); } return optionsBuilder; } /// /// Setup database Contexts /// /// /// /// /// public static IServiceCollection PrepareMySqlDatabasePool(this IServiceCollection serviceCollection, IConfiguration configuration, IWebHostEnvironment environment) { return serviceCollection .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)) .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)) .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)); } }