aboutsummaryrefslogtreecommitdiff
path: root/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Emails.Transactional.Extensions/src/TEmailConfig.cs')
-rw-r--r--lib/Emails.Transactional.Extensions/src/TEmailConfig.cs204
1 files changed, 204 insertions, 0 deletions
diff --git a/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs b/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs
new file mode 100644
index 0000000..7905982
--- /dev/null
+++ b/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs
@@ -0,0 +1,204 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: Emails.Transactional.Client.Extensions
+* File: TEmailConfig.cs
+*
+* TEmailConfig.cs is part of Emails.Transactional.Client.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* Emails.Transactional.Client.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* Emails.Transactional.Client.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Text;
+using System.Text.Json.Serialization;
+
+using RestSharp;
+
+using VNLib.Utils.Logging;
+using VNLib.Plugins;
+using VNLib.Net.Rest.Client;
+using VNLib.Net.Rest.Client.OAuth2;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace Emails.Transactional.Client.Extensions
+{
+ /// <summary>
+ /// Provides transactional emails based on templates and variable complilation. Should be loaded as a singleton for a plugin
+ /// </summary>
+ [ConfigurationName(EMAIL_CONFIG_KEY)]
+ public class TEmailConfig : TransactionalEmailConfig, IAsyncConfigurable, IDisposable
+ {
+ const string DEFAULT_USER_AGENT = "VN Transactional Email System";
+
+ public const string EMAIL_CONFIG_KEY = "emails";
+ public const string REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY = "required_email_templates";
+
+ public const int DEFAULT_MAX_CLIENTS = 5;
+ public const int DEFAULT_CLIENT_TIMEOUT_MS = 10000;
+
+ private bool disposedValue;
+
+ private readonly OAuth2Authenticator _authenticator;
+
+ /// <summary>
+ /// A shared <see cref="VNLib.Net.Rest.Client.RestClientPool"/> for renting configuraed
+ /// <see cref="RestClient"/>
+ /// </summary>
+ public RestClientPool RestClientPool { get; }
+ /// <summary>
+ /// A global from email address name
+ /// </summary>
+ public string EmailFromName { get; }
+ /// <summary>
+ /// A global from email address
+ /// </summary>
+ public string EmailFromAddress { get; }
+
+ private SecretResult? _clientId;
+ private SecretResult? _clientSecret;
+
+
+ /// <summary>
+ /// Intialzies the singleton using the <see cref="LoadingExtensions"/>
+ /// loading system
+ /// </summary>
+ /// <param name="plugin">The plugin loading the instance</param>
+ /// <param name="config">The configuration scope for the instance</param>
+ public TEmailConfig(PluginBase plugin, IConfigScope config)
+ {
+ //The current config scope should be the required data, so we can deserialze it
+ JsonConfig mailConfig = config.DeserialzeAndValidate<JsonConfig>();
+
+ //Get required templates
+ Dictionary<string, string> templates = plugin.GetConfig(REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY).Deserialze<Dictionary<string, string>>();
+
+ EmailFromName = mailConfig.EmailFromName!;
+ EmailFromAddress = mailConfig.EmailFromAddress!;
+
+ if (plugin.IsDebug())
+ {
+ plugin.Log.Debug("Required email templates {t}", templates);
+ }
+
+ //Options for auth token endpoint
+ RestClientOptions clientOptions = new()
+ {
+ AllowMultipleDefaultParametersWithSameName = true,
+ //Server supports compression
+ AutomaticDecompression = System.Net.DecompressionMethods.All,
+ PreAuthenticate = false,
+ Encoding = Encoding.UTF8,
+ MaxTimeout = mailConfig.TimeoutMs,
+ UserAgent = mailConfig.UserAgent,
+ //Server should not redirect
+ FollowRedirects = false
+ };
+
+ //Init Oauth authenticator
+ _authenticator = new(clientOptions, OAuthCredentialGet, mailConfig.TokenServerUri.ToString());
+
+ //Create client pool
+ RestClientPool = new(mailConfig.MaxClients, clientOptions, authenticator: _authenticator);
+
+ //Init templates and service url
+ WithTemplates(templates)
+ .WithUrl(mailConfig.ServiceUri);
+ }
+
+ private Credential OAuthCredentialGet()
+ {
+ _ = _clientId ?? throw new InvalidOperationException("Oauth2 client id was not loaded");
+ _ = _clientSecret ?? throw new InvalidOperationException("Oauth2 client secret was not loaded");
+
+ //Create new credential
+ return Credential.Create(_clientId.Result, _clientSecret.Result);
+ }
+
+ async Task IAsyncConfigurable.ConfigureServiceAsync(PluginBase plugin)
+ {
+ //Load oauth secrets
+ _clientId = await plugin.TryGetSecretAsync("email_client_id");
+ _clientSecret = await plugin.TryGetSecretAsync("email_client_secret");
+ }
+
+ ///<inheritdoc/>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ _clientId?.Dispose();
+ _clientSecret?.Dispose();
+
+ _authenticator.Dispose();
+ RestClientPool.Dispose();
+ }
+ disposedValue = true;
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private sealed class JsonConfig : IOnConfigValidation
+ {
+ [JsonPropertyName("from_name")]
+ public string? EmailFromName { get; set; }
+ [JsonPropertyName("from_address")]
+ public string? EmailFromAddress { get; set; }
+ [JsonPropertyName("email_url")]
+ public string? ServiceUrl { get; set; }
+ [JsonPropertyName("auth_url")]
+ public string? AuthUrl { get; set; }
+ [JsonPropertyName("user_agent")]
+ public string UserAgent { get; set; } = DEFAULT_USER_AGENT;
+ [JsonPropertyName("timeout_ms")]
+ public int TimeoutMs { get; set; } = DEFAULT_CLIENT_TIMEOUT_MS;
+ [JsonPropertyName("max_clients")]
+ public int MaxClients { get; set; } = DEFAULT_MAX_CLIENTS;
+
+
+ [JsonIgnore]
+ public Uri ServiceUri => new(ServiceUrl!);
+
+ [JsonIgnore]
+ public Uri TokenServerUri => new(AuthUrl!);
+
+ void IOnConfigValidation.Validate()
+ {
+ _ = EmailFromName ?? throw new KeyNotFoundException("Missing required mail configuration key 'from_name'");
+ _ = EmailFromAddress ?? throw new KeyNotFoundException("Missing required mail configuration key 'from_address'");
+ _ = ServiceUri ?? throw new KeyNotFoundException("Missing required mail configuration key 'email_url'");
+ _ = AuthUrl ?? throw new KeyNotFoundException("Missing required mail configuration key 'auth_url'");
+
+ if (MaxClients < 1)
+ {
+ throw new ArgumentOutOfRangeException("max_client", "Max clients config variable must be a non-zero positive integer");
+ }
+
+ if (TimeoutMs < 100)
+ {
+ throw new ArgumentOutOfRangeException("timeout_ms", "Client timeout should be a value greater than 100ms");
+ }
+ }
+ }
+ }
+} \ No newline at end of file