profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
UsersModuleExtensions.cs 6,174 bytes
1 using System.Text;
2 using Microsoft.AspNetCore.Authentication.JwtBearer;
3 using Microsoft.AspNetCore.Builder;
4 using Microsoft.AspNetCore.DataProtection;
5 using Microsoft.AspNetCore.Identity;
6 using Microsoft.EntityFrameworkCore;
7 using Microsoft.Extensions.Configuration;
8 using Microsoft.Extensions.DependencyInjection;
9 using Microsoft.IdentityModel.Tokens;
10 using SplitApp.Modules.Users.Application;
11 using SplitApp.Modules.Users.Application.Contracts;
12 using SplitApp.Modules.Users.Application.Services;
13 using SplitApp.Modules.Users.Domain.Entities;
14 using SplitApp.Modules.Users.Infrastructure.Persistence;
15 using SplitApp.Modules.Users.Infrastructure.Persistence.Repositories;
16
17 namespace SplitApp.Modules.Users.Infrastructure;
18
19 public static class UsersModuleExtensions
20 {
21 public static IServiceCollection AddUsersModule(
22 this IServiceCollection services,
23 IConfiguration configuration)
24 {
25 services.AddDbContext<UsersDbContext>(opt =>
26 {
27 opt.UseNpgsql(configuration.GetConnectionString("DefaultConnection"));
28 });
29
30 services.AddIdentity<AppUser, AppRole>(options =>
31 {
32 options.Password.RequireDigit = false;
33 options.Password.RequireLowercase = false;
34 options.Password.RequireNonAlphanumeric = false;
35 options.Password.RequireUppercase = false;
36 options.Password.RequiredLength = 6;
37 options.User.RequireUniqueEmail = true;
38 })
39 .AddDefaultUI()
40 .AddEntityFrameworkStores<UsersDbContext>()
41 .AddDefaultTokenProviders();
42
43 services.AddDataProtection().PersistKeysToDbContext<UsersDbContext>();
44
45 services.AddAuthentication()
46 .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
47 {
48 options.RequireHttpsMetadata = false;
49 options.SaveToken = false;
50 options.TokenValidationParameters = new TokenValidationParameters
51 {
52 ValidateIssuer = true,
53 ValidateAudience = true,
54 ValidateLifetime = true,
55 ValidateIssuerSigningKey = true,
56 ValidIssuer = configuration["JWT:Issuer"],
57 ValidAudience = configuration["JWT:Audience"],
58 IssuerSigningKey = new SymmetricSecurityKey(
59 Encoding.UTF8.GetBytes(configuration["JWT:Key"]!)),
60 ClockSkew = TimeSpan.Zero
61 };
62 });
63
64 services.AddScoped<IUserRepository, UserRepository>();
65 services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
66 services.AddScoped<IUsersUnitOfWork, UsersUnitOfWork>();
67 services.AddScoped<IIdentityService, IdentityService>();
68
69 services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<UsersModuleMarker>());
70
71 return services;
72 }
73
74 public static IApplicationBuilder UseUsersModule(this IApplicationBuilder app)
75 {
76 using var scope = app.ApplicationServices.CreateScope();
77 var db = scope.ServiceProvider.GetRequiredService<UsersDbContext>();
78 db.Database.Migrate();
79
80 var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<AppRole>>();
81 foreach (var roleName in new[] { "admin", "user" })
82 {
83 if (!roleManager.RoleExistsAsync(roleName).GetAwaiter().GetResult())
84 {
85 roleManager.CreateAsync(new AppRole { Name = roleName }).GetAwaiter().GetResult();
86 }
87 }
88
89 var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
90
91 // The demo password for the ordinary accounts is in the source on
92 // purpose: the running instance is a demo and anyone reading the code is
93 // meant to be able to sign in and look around.
94 //
95 // The administrator is not. That account can change other people's data,
96 // and this source is shared read-only with people outside the project, so
97 // its password comes from SEED_ADMIN_PASSWORD and there is no default.
98 // No variable, no administrator: seeding skips the account rather than
99 // falling back to something guessable.
100 const string demoPassword = "Kala.12345";
101 var adminPassword = Environment.GetEnvironmentVariable("SEED_ADMIN_PASSWORD");
102
103 var seedUsers = new[]
104 {
105 (Email: "user@taltech.ee", Password: demoPassword, FirstName: "Test", LastName: "User", Roles: new[] { "user" }),
106 (Email: "alice@taltech.ee", Password: demoPassword, FirstName: "Alice", LastName: "Johnson", Roles: new[] { "user" }),
107 (Email: "bob@taltech.ee", Password: demoPassword, FirstName: "Bob", LastName: "Smith", Roles: new[] { "user" }),
108 (Email: "charlie@taltech.ee", Password: demoPassword, FirstName: "Charlie", LastName: "Brown", Roles: new[] { "user" }),
109 (Email: "diana@taltech.ee", Password: demoPassword, FirstName: "Diana", LastName: "Miller", Roles: new[] { "user" }),
110 };
111
112 if (!string.IsNullOrWhiteSpace(adminPassword))
113 {
114 seedUsers =
115 [
116 (Email: "admin@taltech.ee", Password: adminPassword, FirstName: "Admin", LastName: "User", Roles: new[] { "admin" }),
117 .. seedUsers,
118 ];
119 }
120
121 foreach (var seed in seedUsers)
122 {
123 var existing = userManager.FindByEmailAsync(seed.Email).GetAwaiter().GetResult();
124 if (existing != null) continue;
125
126 var user = new AppUser
127 {
128 UserName = seed.Email,
129 Email = seed.Email,
130 EmailConfirmed = true,
131 FirstName = seed.FirstName,
132 LastName = seed.LastName,
133 };
134 var result = userManager.CreateAsync(user, seed.Password).GetAwaiter().GetResult();
135 if (!result.Succeeded) continue;
136 userManager.AddToRolesAsync(user, seed.Roles).GetAwaiter().GetResult();
137 }
138
139 return app;
140 }
141 }
142