From a9e0fd909e41ece18d01653b4b7891a3c6a12841 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Mon, 2 Feb 2026 16:05:22 -0600 Subject: [PATCH] Adding email for sending to owners, admins, and managers to notify that auto confirm feature has been enabled from admin portal --- .../Controllers/OrganizationsController.cs | 18 +- ...OrganizationAutoConfirmationEnabledView.cs | 13 + ...zationAutoConfirmationEnabledView.html.hbs | 497 ++++++++++++++++++ ...zationAutoConfirmationEnabledView.text.hbs | 6 + ...onAutoConfirmEnabledNotificationCommand.cs | 61 +++ .../organization-auto-confirm-enabled.mjml | 45 ++ ...OrganizationServiceCollectionExtensions.cs | 6 + 7 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.cs create mode 100644 src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.html.hbs create mode 100644 src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.text.hbs create mode 100644 src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/OrganizationAutoConfirmEnabledNotificationCommand.cs create mode 100644 src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-auto-confirm-enabled.mjml diff --git a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs index 1dbab08ca6..298a2982da 100644 --- a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs @@ -9,6 +9,7 @@ using Bit.Admin.Utilities; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; using Bit.Core.AdminConsole.Providers.Interfaces; using Bit.Core.AdminConsole.Repositories; @@ -59,6 +60,7 @@ public class OrganizationsController : Controller private readonly IPricingClient _pricingClient; private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand; private readonly IOrganizationBillingService _organizationBillingService; + private readonly IOrganizationAutoConfirmEnabledNotificationCommand _organizationAutoConfirmEnabledNotificationCommand; public OrganizationsController( IOrganizationRepository organizationRepository, @@ -84,7 +86,8 @@ public class OrganizationsController : Controller IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand, IPricingClient pricingClient, IResendOrganizationInviteCommand resendOrganizationInviteCommand, - IOrganizationBillingService organizationBillingService) + IOrganizationBillingService organizationBillingService, + IOrganizationAutoConfirmEnabledNotificationCommand organizationAutoConfirmEnabledNotificationCommand) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -110,6 +113,7 @@ public class OrganizationsController : Controller _pricingClient = pricingClient; _resendOrganizationInviteCommand = resendOrganizationInviteCommand; _organizationBillingService = organizationBillingService; + _organizationAutoConfirmEnabledNotificationCommand = organizationAutoConfirmEnabledNotificationCommand; } [RequirePermission(Permission.Org_List_View)] @@ -293,6 +297,18 @@ public class OrganizationsController : Controller await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); + if (!existingOrganizationData.UseAutomaticUserConfirmation && organization.UseAutomaticUserConfirmation) + { + var emailsToNotify = + (await _organizationUserRepository.GetManyDetailsByOrganizationAsync_vNext(organization.Id)) + .Where(x => x.Type == OrganizationUserType.Admin || x.Type == OrganizationUserType.Owner || + x.GetPermissions().ManageUsers) + .Select(x => x.Email); + + await _organizationAutoConfirmEnabledNotificationCommand.SendEmailAsync( + new OrganizationAutoConfirmEnabledNotificationRequest(organization, [.. emailsToNotify])); + } + // Sync name/email changes to Stripe if (existingOrganizationData.Name != organization.Name || existingOrganizationData.BillingEmail != organization.BillingEmail) { diff --git a/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.cs b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.cs new file mode 100644 index 0000000000..3c87792ff7 --- /dev/null +++ b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.cs @@ -0,0 +1,13 @@ +using Bit.Core.Platform.Mail.Mailer; + +namespace Bit.Core.AdminConsole.Models.Mail.Mailer.OrganizationUserAutoConfirmation; + +public class OrganizationAutoConfirmationEnabledView : BaseMailView +{ + public required string WebVaultUrl { get; set; } +} + +public class OrganizationAutoConfirmationEnabled : BaseMail +{ + public override required string Subject { get; set; } +} diff --git a/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.html.hbs b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.html.hbs new file mode 100644 index 0000000000..c9f6eaad6a --- /dev/null +++ b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.html.hbs @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + +
+ + + + + + + + +
+ + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + +
+ + +
+ +
+ + + + + + + + + +
+ + + + + + + +
+ + + +
+ + + + + + + +
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
Automatic user confirmation is now available!
+ +
+ +
A new global policy is available for your organization. It allows + new users to be automatically confirmed while an admin’s device is + unlocked. Log in to the web app to turn on the policy.
+ +
+ + + + + + + +
+ + Log in + +
+ +
+ + + +
+ +
+ +
+ + +
+ +
+ + + +
+ +
+ + + + + + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + +
+ +

+ © 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa + Barbara, CA, USA +

+

+ Always confirm you are on a trusted Bitwarden domain before logging + in:
+ bitwarden.com + | + Learn why we include this +

+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + \ No newline at end of file diff --git a/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.text.hbs b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.text.hbs new file mode 100644 index 0000000000..890bdef75d --- /dev/null +++ b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationUserAutoConfirmation/OrganizationAutoConfirmationEnabledView.text.hbs @@ -0,0 +1,6 @@ +{{#>BasicTextLayout}} +Automatic user confirmation is now available! + +A new global policy is available for your organization. It allows new users to be automatically confirmed while an +admin’s device is unlocked. Log in to the web app to turn on the policy. +{{/BasicTextLayout}} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/OrganizationAutoConfirmEnabledNotificationCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/OrganizationAutoConfirmEnabledNotificationCommand.cs new file mode 100644 index 0000000000..aece8c930c --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/OrganizationAutoConfirmEnabledNotificationCommand.cs @@ -0,0 +1,61 @@ +using System.Net; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Mail.Mailer.OrganizationUserAutoConfirmation; +using Bit.Core.Platform.Mail.Mailer; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; +using OneOf.Types; +using CommandResult = Bit.Core.AdminConsole.Utilities.v2.Results.CommandResult; +using Error = Bit.Core.AdminConsole.Utilities.v2.Error; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +public record OrganizationAutoConfirmEnabledNotificationRequest(Organization Organization, ICollection Emails); + +public record NoEmailsWereProvided() : Error("No emails were provided"); + +public record EmailSendingFailed() : Error("Failed to send email to organization admins"); + +public interface IOrganizationAutoConfirmEnabledNotificationCommand +{ + Task SendEmailAsync(OrganizationAutoConfirmEnabledNotificationRequest request); +} + +public class OrganizationAutoConfirmEnabledNotificationCommand( + IMailer mailer, + ILogger logger, + GlobalSettings globalSettings) : IOrganizationAutoConfirmEnabledNotificationCommand +{ + public async Task SendEmailAsync(OrganizationAutoConfirmEnabledNotificationRequest request) + { + if (request.Emails.Count == 0) + { + return new NoEmailsWereProvided(); + } + + var mail = new OrganizationAutoConfirmationEnabled + { + ToEmails = request.Emails, + View = new OrganizationAutoConfirmationEnabledView + { + WebVaultUrl = globalSettings.BaseServiceUri.Vault + "#/organizations/" + request.Organization.Id + "/settings/policies" + }, + Subject = $"Automatic user confirmation is available for {WebUtility.HtmlEncode(request.Organization.Name)}" + }; + + try + { + await mailer.SendEmail(mail); + } + catch (Exception ex) + { + logger.LogError(ex, + "Failed to send email to organization admins for Auto Confirm feature enablement. Organization: {OrganizationId}", + request.Organization.Id); + + return new EmailSendingFailed(); + } + + return new None(); + } +} diff --git a/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-auto-confirm-enabled.mjml b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-auto-confirm-enabled.mjml new file mode 100644 index 0000000000..8f881d0b69 --- /dev/null +++ b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-auto-confirm-enabled.mjml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + Automatic user confirmation is now available! + + + A new global policy is available for your organization. It allows + new users to be automatically confirmed while an admin’s device is + unlocked. Log in to the web app to turn on the policy. + + Log in + + Learn more about this policy + + + + + + + + + diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index c1ebc65d44..1ebe1a5d5e 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -73,6 +73,12 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationUserCommands(); services.AddOrganizationUserCommandsQueries(); services.AddBaseOrganizationSubscriptionCommandsQueries(); + services.AddOrganizationFeatureCommands(); + } + + private static void AddOrganizationFeatureCommands(this IServiceCollection services) + { + services.AddScoped(); } private static void AddOrganizationSignUpCommands(this IServiceCollection services)