using Microsoft.EntityFrameworkCore; /// /// One-shot bootstrap: creates the first Admin user when the ClinicalUsers /// table is empty. Refuses to run if any user already exists so it cannot /// be used as a backdoor after the system is in use. /// /// Usage: /// dotnet VigilCareClinicalAPI.dll create-admin --username <u> --password <p> --display-name "<n>" /// public static class CreateAdminCommand { public static async Task RunAsync(IServiceProvider services, string[] args) { var username = RequireArg(args, "--username"); var password = RequireArg(args, "--password"); var displayName = RequireArg(args, "--display-name"); if (username.Length > 100) { Console.Error.WriteLine("Username must be 100 characters or fewer."); return 1; } if (password.Length < 8) { Console.Error.WriteLine("Password must be at least 8 characters."); return 1; } if (displayName.Length > 200) { Console.Error.WriteLine("Display name must be 200 characters or fewer."); return 1; } using var scope = services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); if (await db.ClinicalUsers.AnyAsync()) { Console.Error.WriteLine( "Refusing to create admin: ClinicalUsers already contains at least one row. " + "create-admin is a one-shot bootstrap and cannot be used as a backdoor."); return 1; } var user = new ClinicalUser { Id = Guid.NewGuid(), Username = username.Trim(), PasswordHash = BCrypt.Net.BCrypt.HashPassword(password), DisplayName = displayName.Trim(), Role = ClinicalRole.Admin, IsActive = true, CreatedAt = DateTimeOffset.UtcNow, }; db.ClinicalUsers.Add(user); await db.SaveChangesAsync(); Console.WriteLine($"Created admin user '{user.Username}' (id={user.Id})."); return 0; } private static string RequireArg(string[] args, string name) { for (var i = 0; i < args.Length - 1; i++) { if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) { var value = args[i + 1]; if (string.IsNullOrWhiteSpace(value) || value.StartsWith("--", StringComparison.Ordinal)) break; return value; } } throw new ArgumentException($"Missing required argument: {name}"); } }