From 39af0fb9a5e531d9b3cdea7b987fca0872ee7962 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 12 Jul 2023 20:46:01 -0400 Subject: [PATCH] Implement e-mail changes - Add canonical domain handling - Bump version --- src/.dockerignore | 3 ++- src/Directory.Build.props | 6 +++--- src/PrayerTracker/App.fs | 15 ++++++++----- src/PrayerTracker/Email.fs | 30 ++++++++++++++++++++------ src/PrayerTracker/PrayerRequest.fs | 3 ++- src/PrayerTracker/PrayerTracker.fsproj | 1 + src/PrayerTracker/SmallGroup.fs | 3 ++- 7 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/.dockerignore b/src/.dockerignore index a267a97..7364d76 100644 --- a/src/.dockerignore +++ b/src/.dockerignore @@ -1,2 +1,3 @@ **/bin/* -**/obj/* \ No newline at end of file +**/obj/* +**/appsettings.* diff --git a/src/Directory.Build.props b/src/Directory.Build.props index d0aaa2c..72b2f28 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,11 +1,11 @@ net7.0 - 8.1.0.0 - 8.1.0.0 + 8.2.0.0 + 8.2.0.0 danieljsummers Bit Badger Solutions - 8.1.0 + 8.2.0 Embedded diff --git a/src/PrayerTracker/App.fs b/src/PrayerTracker/App.fs index 485fd1b..2b9178a 100644 --- a/src/PrayerTracker/App.fs +++ b/src/PrayerTracker/App.fs @@ -70,6 +70,9 @@ module Configure = let _ = dsb.UseNodaTime() Configuration.useDataSource (dsb.Build ()) + let emailCfg = cfg.GetSection "Email" + if (emailCfg.GetChildren >> Seq.isEmpty >> not) () then ConfigurationBinder.Bind(emailCfg, Email.smtpOptions) + let _ = svc.AddSingleton () let _ = svc.AddSession () let _ = svc.AddAntiforgery () @@ -182,19 +185,21 @@ module Configure = |> function l -> l.AddConsole().AddDebug() |> ignore + open BitBadger.AspNetCore.CanonicalDomains open Microsoft.Extensions.Localization open Microsoft.Extensions.Options /// Configure the application let app (app : IApplicationBuilder) = - let env = app.ApplicationServices.GetRequiredService() + let env = app.ApplicationServices.GetRequiredService () if env.IsDevelopment () then - let _ = app.UseDeveloperExceptionPage () - () + app.UseDeveloperExceptionPage () else - let _ = app.UseGiraffeErrorHandler errorHandler - () + app.UseGiraffeErrorHandler errorHandler + |> ignore + let _ = app.UseForwardedHeaders () + let _ = app.UseCanonicalDomains () let _ = app.UseStatusCodePagesWithReExecute "/error/{0}" let _ = app.UseStaticFiles () let _ = app.UseCookiePolicy (CookiePolicyOptions (MinimumSameSitePolicy = SameSiteMode.Strict)) diff --git a/src/PrayerTracker/Email.fs b/src/PrayerTracker/Email.fs index c559910..28d190a 100644 --- a/src/PrayerTracker/Email.fs +++ b/src/PrayerTracker/Email.fs @@ -30,23 +30,39 @@ type EmailOptions = Strings : IStringLocalizer } -/// The e-mail address from which e-mail is sent -let private fromAddress = "prayer@bitbadger.solutions" +/// Options to use when sending e-mail +type SmtpServerOptions() = + /// The hostname of the SMTP server + member val SmtpHost : string = "localhost" with get, set -open MailKit.Security -open Microsoft.Extensions.Configuration + /// The port over which SMTP communication should occur + member val Port : int = 25 with get, set + + /// Whether to use SSL when communicating with the SMTP server + member val UseSsl : bool = false with get, set + + /// The authentication to use with the SMTP server + member val Authentication : string = "" with get, set + + /// The e-mail address from which messages should be sent + member val FromAddress : string = "prayer@bitbadger.solutions" with get, set + + +/// The options for the SMTP server +let smtpOptions = SmtpServerOptions () /// Get an SMTP client connection -let getConnection (cfg : IConfiguration) = task { +let getConnection () = task { let client = new SmtpClient () - do! client.ConnectAsync (cfg.GetConnectionString "SmtpServer", 25, SecureSocketOptions.None) + do! client.ConnectAsync (smtpOptions.SmtpHost, smtpOptions.Port, smtpOptions.UseSsl) + do! client.AuthenticateAsync (smtpOptions.FromAddress, smtpOptions.Authentication) return client } /// Create a mail message object, filled with everything but the body content let createMessage opts = let msg = new MimeMessage () - msg.From.Add (MailboxAddress (opts.Group.Preferences.EmailFromName, fromAddress)) + msg.From.Add (MailboxAddress (opts.Group.Preferences.EmailFromName, smtpOptions.FromAddress)) msg.Subject <- opts.Subject msg.ReplyTo.Add (MailboxAddress (opts.Group.Preferences.EmailFromName, opts.Group.Preferences.EmailFromAddress)) msg diff --git a/src/PrayerTracker/PrayerRequest.fs b/src/PrayerTracker/PrayerRequest.fs index 527ff15..732a228 100644 --- a/src/PrayerTracker/PrayerRequest.fs +++ b/src/PrayerTracker/PrayerRequest.fs @@ -88,7 +88,7 @@ let email date : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task { let! list = generateRequestList ctx listDate let group = ctx.Session.CurrentGroup.Value let! recipients = Members.forGroup group.Id - use! client = Email.getConnection (ctx.GetService ()) + use! client = Email.getConnection () do! Email.sendEmails { Client = client Recipients = recipients @@ -98,6 +98,7 @@ let email date : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task { PlainTextBody = list.AsText s Strings = s } + do! client.DisconnectAsync true return! viewInfo ctx |> Views.PrayerRequest.email { list with Recipients = recipients } diff --git a/src/PrayerTracker/PrayerTracker.fsproj b/src/PrayerTracker/PrayerTracker.fsproj index 583871d..19dc847 100644 --- a/src/PrayerTracker/PrayerTracker.fsproj +++ b/src/PrayerTracker/PrayerTracker.fsproj @@ -24,6 +24,7 @@ + diff --git a/src/PrayerTracker/SmallGroup.fs b/src/PrayerTracker/SmallGroup.fs index 3629d55..6fe720a 100644 --- a/src/PrayerTracker/SmallGroup.fs +++ b/src/PrayerTracker/SmallGroup.fs @@ -269,7 +269,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> return users |> List.map (fun u -> { Member.empty with Name = u.Name; Email = u.Email }) else return! Members.forGroup group.Id } - use! client = Email.getConnection (ctx.GetService ()) + use! client = Email.getConnection () do! Email.sendEmails { Client = client Recipients = recipients @@ -280,6 +280,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> PlainTextBody = plainText Strings = s } + do! client.DisconnectAsync true // Add to the request list if desired match model.SendToClass, model.AddToRequestList with | "N", _