/// <summary> /// Helper function. /// </summary> private async Task <EmbeddedIdentityServerUser> GetOrCreateUser(string email, bool emailConfirmed) { var idUser = await _userManager.FindByNameAsync(email) ?? await _userManager.FindByEmailAsync(email); if (idUser == null) { // Create the identity user if it doesn't exist idUser = new EmbeddedIdentityServerUser { UserName = email, Email = email, EmailConfirmed = emailConfirmed }; var result = await _userManager.CreateAsync(idUser); if (!result.Succeeded) { string msg = string.Join(", ", result.Errors.Select(e => e.Description)); throw new InvalidOperationException(msg); } } return(idUser); }
private async Task LoadSharedKeyAndQrCodeUriAsync(EmbeddedIdentityServerUser user) { // Load the authenticator key & QR code URI to display on the form var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); if (string.IsNullOrEmpty(unformattedKey)) { await _userManager.ResetAuthenticatorKeyAsync(user); unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); } SharedKey = FormatKey(unformattedKey); var email = await _userManager.GetEmailAsync(user); AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey); }
private async Task <(string Subject, string Body)> MakeInvitationEmailAsync(EmbeddedIdentityServerUser identityRecipient, string name, string name2, string name3, string preferredLang) { // Load the info var info = await _appRepo.GetTenantInfoAsync(); // Use the recipient's preferred Language CultureInfo culture = string.IsNullOrWhiteSpace(preferredLang) ? CultureInfo.CurrentUICulture : new CultureInfo(preferredLang); var localizer = _localizer.WithCulture(culture); // Prepare the parameters string userId = identityRecipient.Id; string emailToken = await _userManager.GenerateEmailConfirmationTokenAsync(identityRecipient); string passwordToken = await _userManager.GeneratePasswordResetTokenAsync(identityRecipient); string nameOfInvitor = info.SecondaryLanguageId == culture.Name ? info.ShortCompanyName2 ?? info.ShortCompanyName : info.TernaryLanguageId == culture.Name ? info.ShortCompanyName3 ?? info.ShortCompanyName : info.ShortCompanyName; string nameOfRecipient = info.SecondaryLanguageId == name ? name2 ?? name : info.TernaryLanguageId == name ? name3 ?? name : name; string callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { userId, code = emailToken, passwordCode = passwordToken, area = "Identity" }, protocol: Request.Scheme); // Prepare the email string emailSubject = localizer["InvitationEmailSubject0", localizer["AppName"]]; string emailBody = _emailTemplates.MakeInvitationEmail( nameOfRecipient: nameOfRecipient, nameOfInvitor: nameOfInvitor, validityInDays: Constants.TokenExpiryInDays, userId: userId, callbackUrl: callbackUrl, culture: culture); return(emailSubject, emailBody); }
/// <summary> /// Database initialization is performed here, after the web host is configured but before it is run /// this way the initialization has access to environment variables in configuration providers, but it /// only runs once when the web app loads /// </summary> public static void InitDatabase(IServiceProvider provider) { // If missing, the default admin user is added here using var scope = provider.CreateScope(); // (1) Retrieve the admin credentials from configurations var opt = scope.ServiceProvider.GetRequiredService <IOptions <GlobalOptions> >().Value; string email = opt?.Admin?.Email ?? "*****@*****.**"; string fullName = opt?.Admin?.FullName ?? "Administrator"; string password = opt?.Admin?.Password ?? "Admin@123"; // (2) Create the user in the admin database var adminRepo = scope.ServiceProvider.GetRequiredService <AdminRepository>(); adminRepo.AdminUsers__CreateAdmin(email, fullName, password).Wait(); // (3) Create the user in the embedded identity server (if enabled) if (opt.EmbeddedIdentityServerEnabled) { var userManager = scope.ServiceProvider.GetRequiredService <UserManager <EmbeddedIdentityServerUser> >(); var admin = userManager.FindByEmailAsync(email).GetAwaiter().GetResult(); if (admin == null) { admin = new EmbeddedIdentityServerUser { UserName = email, Email = email, EmailConfirmed = true }; var result = userManager.CreateAsync(admin, password).GetAwaiter().GetResult(); if (!result.Succeeded) { string msg = string.Join(", ", result.Errors.Select(e => e.Description)); throw new Exception($"Failed to create the administrator account. Message: {msg}"); } } } }
protected override async Task <List <int> > SaveExecuteAsync(List <UserForSave> entities, ExpandExpression expand, bool returnIds) { // NOTE: this method is not optimized for massive bulk (e.g. 1,000+ users), since it relies // on querying identity through UserManager one email at a time but it should be acceptable // with the usual workloads, customers with more than 200 users are rare anyways // Step (1) enlist the app repo _appRepo.EnlistTransaction(Transaction.Current); // So that it is not affected by admin trx scope later // Step (2): If Embedded Identity Server is enabled, create any emails that don't already exist there var usersToInvite = new List <(EmbeddedIdentityServerUser IdUser, UserForSave User)>(); if (_options.EmbeddedIdentityServerEnabled) { _identityTrxScope = ControllerUtilities.CreateTransaction(TransactionScopeOption.RequiresNew); foreach (var entity in entities) { var email = entity.Email; // In case the user was added in a previous failed transaction // or something, we always try to be forgiving in the code var identityUser = await _userManager.FindByNameAsync(email) ?? await _userManager.FindByEmailAsync(email); // This is truly a new user, create it if (identityUser == null) { // Create the identity user identityUser = new EmbeddedIdentityServerUser { UserName = email, Email = email, EmailConfirmed = !_options.EmailEnabled // Note: If the system is integrated with an email service, user emails // are automatically confirmed, otherwise users must confirm their }; var result = await _userManager.CreateAsync(identityUser); if (!result.Succeeded) { string msg = string.Join(", ", result.Errors.Select(e => e.Description)); _logger.LogError(msg); throw new BadRequestException($"An unexpected error occurred while creating an account for '{email}'"); } } // Mark for invitation later if (!identityUser.EmailConfirmed) { usersToInvite.Add((identityUser, entity)); } } } // Step (3): Extract the images var(blobsToDelete, blobsToSave, imageIds) = await ImageUtilities.ExtractImages <User, UserForSave>(_appRepo, entities, BlobName); // Step (4): Save the users in the app database var ids = await _appRepo.Users__Save(entities, imageIds, returnIds); // Step (5): Delete old images from the blob storage if (blobsToDelete.Any()) { await _blobService.DeleteBlobsAsync(blobsToDelete); } // Step (6): Save new images to the blob storage if (blobsToSave.Any()) { await _blobService.SaveBlobsAsync(blobsToSave); } // Step (7) Same the emails in the admin database var tenantId = _tenantIdAccessor.GetTenantId(); _adminTrxScope = ControllerUtilities.CreateTransaction(TransactionScopeOption.RequiresNew); _adminRepo.EnlistTransaction(Transaction.Current); var oldEmails = new List <string>(); // Emails are readonly after the first save var newEmails = entities.Where(e => e.Id == 0).Select(e => e.Email); await _adminRepo.GlobalUsers__Save(newEmails, oldEmails, tenantId); // Step (8): Send the invitation emails if (usersToInvite.Any()) // This will be empty if embedded identity is disabled or if email is disabled { var userIds = usersToInvite.Select(e => e.User.Id).ToArray(); var tos = new List <string>(); var subjects = new List <string>(); var substitutions = new List <Dictionary <string, string> >(); foreach (var(idUser, user) in usersToInvite) { // Add the email sender parameters var(subject, body) = await MakeInvitationEmailAsync(idUser, user.Name, user.Name2, user.Name3, user.PreferredLanguage); tos.Add(idUser.Email); subjects.Add(subject); substitutions.Add(new Dictionary <string, string> { { "-message-", body } }); } await _emailSender.SendEmailBulkAsync( tos : tos, subjects : subjects, htmlMessage : $"-message-", substitutions : substitutions.ToList() ); } // Return the new Ids return(ids); }