Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ce497b37e | |||
| aceef9ff74 | |||
| 445fc2b858 | |||
| 9dcbf0d958 | |||
| b17baca835 |
@@ -0,0 +1 @@
|
|||||||
|
-
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
[submodule "OCORE"]
|
||||||
|
path = OCORE
|
||||||
|
url = https://git.processweb.de/Stefan/OCORE.git
|
||||||
|
[submodule "OCORE_web"]
|
||||||
|
path = OCORE_web
|
||||||
|
url = https://git.processweb.de/Stefan/OCORE_web.git
|
||||||
|
[submodule "OCORE_web_pdf"]
|
||||||
|
path = OCORE_web_pdf
|
||||||
|
url = https://git.processweb.de/Stefan/OCORE_web_pdf.git
|
||||||
|
[submodule "OCORE_Charting"]
|
||||||
|
path = OCORE_Charting
|
||||||
|
url = https://git.processweb.de/Stefan/OCORE_Charting.git
|
||||||
@@ -9,14 +9,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
<PackageReference Include="coverlet.collector" Version="10.0.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
|
using Fuchs.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -117,11 +118,11 @@ public partial class IntranetController
|
|||||||
frdic.no("InvoiceFile", null!) is byte[] invFile)
|
frdic.no("InvoiceFile", null!) is byte[] invFile)
|
||||||
remdoc[frdic.nz("InvoiceFileName")] = invFile;
|
remdoc[frdic.nz("InvoiceFileName")] = invFile;
|
||||||
|
|
||||||
bool sent = await FuchsFdsEmail.SendEmail(
|
bool sent = await _comService.SendEmailAsync(
|
||||||
$"inv_{remId}",
|
$"inv_{remId}",
|
||||||
$"SanitärFuchs - {frdic.nz("subject").ne(frdic.nz("DocumentName"))}",
|
$"SanitärFuchs - {frdic.nz("subject").ne(frdic.nz("DocumentName"))}",
|
||||||
BuildReminderBody(Convert.ToDouble(frdic.no("amount_open", 0))),
|
BuildReminderBody(Convert.ToDouble(frdic.no("amount_open", 0))),
|
||||||
email.Trim(), "", remdoc, _intranet);
|
email.Trim(), "", remdoc);
|
||||||
if (sent)
|
if (sent)
|
||||||
{
|
{
|
||||||
var pls = StdParamlist(SQL_VarChar("@Id", remId), SQL_Bit("@auto", true));
|
var pls = StdParamlist(SQL_VarChar("@Id", remId), SQL_Bit("@auto", true));
|
||||||
@@ -175,10 +176,10 @@ public partial class IntranetController
|
|||||||
if (!string.IsNullOrEmpty(frdic.nz("InvoiceFileName")) &&
|
if (!string.IsNullOrEmpty(frdic.nz("InvoiceFileName")) &&
|
||||||
frdic.no("InvoiceFile", null!) is byte[] invFile)
|
frdic.no("InvoiceFile", null!) is byte[] invFile)
|
||||||
remdoc[frdic.nz("InvoiceFileName")] = invFile;
|
remdoc[frdic.nz("InvoiceFileName")] = invFile;
|
||||||
await FuchsFdsEmail.SendEmail($"rem_{remId}",
|
await _comService.SendEmailAsync($"rem_{remId}",
|
||||||
$"SanitärFuchs - {frdic.nz("subject").ne(frdic.nz("DocumentName"))}",
|
$"SanitärFuchs - {frdic.nz("subject").ne(frdic.nz("DocumentName"))}",
|
||||||
BuildReminderBody(Convert.ToDouble(frdic.no("amount_open", 0))),
|
BuildReminderBody(Convert.ToDouble(frdic.no("amount_open", 0))),
|
||||||
email.Trim(), "", remdoc, _intranet);
|
email.Trim(), "", remdoc);
|
||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
|
using Fuchs.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using MFR_RESTClient.generic;
|
using MFR_RESTClient.generic;
|
||||||
@@ -273,9 +274,9 @@ public partial class IntranetController
|
|||||||
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
|
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
|
||||||
string terms = fdInv.PaymentTerms.Replace("wd", " Werktagen").Replace("d", " Tagen").Replace("wk", " Wochen").ne("10 Tagen");
|
string terms = fdInv.PaymentTerms.Replace("wd", " Werktagen").Replace("d", " Tagen").Replace("wk", " Wochen").ne("10 Tagen");
|
||||||
string body = BuildInvoiceBody(bal, terms);
|
string body = BuildInvoiceBody(bal, terms);
|
||||||
bool sent = await FuchsFdsEmail.SendEmail(
|
bool sent = await _comService.SendEmailAsync(
|
||||||
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
|
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
|
||||||
body, email.Trim(), "", inv, _intranet);
|
body, email.Trim(), "", inv);
|
||||||
if (sent)
|
if (sent)
|
||||||
{
|
{
|
||||||
var pls = StdParamlist(SQL_VarChar("@Id", invId), SQL_Bit("@auto", true));
|
var pls = StdParamlist(SQL_VarChar("@Id", invId), SQL_Bit("@auto", true));
|
||||||
@@ -328,11 +329,10 @@ public partial class IntranetController
|
|||||||
{
|
{
|
||||||
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
|
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
|
||||||
string terms = fdInv.PaymentTerms.Replace("wd", " Werktagen").Replace("d", " Tagen").Replace("wk", " Wochen").ne("10 Tagen");
|
string terms = fdInv.PaymentTerms.Replace("wd", " Werktagen").Replace("d", " Tagen").Replace("wk", " Wochen").ne("10 Tagen");
|
||||||
await FuchsFdsEmail.SendEmail(
|
await _comService.SendEmailAsync(
|
||||||
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
|
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
|
||||||
BuildInvoiceBody(bal, terms), email.Trim(), "",
|
BuildInvoiceBody(bal, terms), email.Trim(), "",
|
||||||
new Dictionary<string, byte[]> { [frdic.nz("DocumentName")] = filebyte },
|
new Dictionary<string, byte[]> { [frdic.nz("DocumentName")] = filebyte });
|
||||||
_intranet);
|
|
||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
|
using Fuchs.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -24,6 +25,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
internal readonly Fuchs_intranet _intranet;
|
internal readonly Fuchs_intranet _intranet;
|
||||||
internal readonly fds.IFdsMfr _mfr;
|
internal readonly fds.IFdsMfr _mfr;
|
||||||
private readonly ILogger<IntranetController> _logger;
|
private readonly ILogger<IntranetController> _logger;
|
||||||
|
private readonly IComService _comService;
|
||||||
private readonly List<string> _allowedNonAuth = new() { "spwc", "spw" };
|
private readonly List<string> _allowedNonAuth = new() { "spwc", "spw" };
|
||||||
private readonly List<string> _allowedGet = new()
|
private readonly List<string> _allowedGet = new()
|
||||||
{
|
{
|
||||||
@@ -39,11 +41,12 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
public string UserAccountID => UserIdent.UserAccountId;
|
public string UserAccountID => UserIdent.UserAccountId;
|
||||||
public string AuthAccount => UserIdent.Email;
|
public string AuthAccount => UserIdent.Email;
|
||||||
|
|
||||||
public IntranetController(Fuchs_intranet intranet, fds.IFdsMfr mfr, ILogger<IntranetController> logger)
|
public IntranetController(Fuchs_intranet intranet, fds.IFdsMfr mfr, ILogger<IntranetController> logger, IComService comService)
|
||||||
{
|
{
|
||||||
_intranet = intranet;
|
_intranet = intranet;
|
||||||
_mfr = mfr;
|
_mfr = mfr;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_comService = comService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Standard param list (pre-populates @authuser) ────────────────────────
|
// ── Standard param list (pre-populates @authuser) ────────────────────────
|
||||||
@@ -203,11 +206,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
row.nz("name").ToLower().Trim() == lastname.ToLower().Trim() &&
|
row.nz("name").ToLower().Trim() == lastname.ToLower().Trim() &&
|
||||||
row.nz("mobile").Length > 5 && !Request.Host.Host.ToLower().Contains("localhost"))
|
row.nz("mobile").Length > 5 && !Request.Host.Host.ToLower().Contains("localhost"))
|
||||||
{
|
{
|
||||||
OCORE.sms.SMS77.Settings.APIKey = _intranet.Intranet__SMS_API_key;
|
|
||||||
using var sms = new OCORE.sms.SMS77.SMS("ProcessWeb");
|
|
||||||
string totp = OCORE.security.TFA.generateTotp_12h(_intranet.Intranet__TOTPsharedsecret_base);
|
string totp = OCORE.security.TFA.generateTotp_12h(_intranet.Intranet__TOTPsharedsecret_base);
|
||||||
if (long.TryParse(row.nz("mobile").Replace("+", "00").Replace(" ", ""), out long smsNum))
|
await _comService.SendSmsAsync(row.nz("mobile"),
|
||||||
sms.SendSMS_sync(smsNum,
|
|
||||||
"Zur Bestätigung des Passwortversands auf sanitarfuchs.de, verwenden Sie bitte folgenden Code:" + totp);
|
"Zur Bestätigung des Passwortversands auf sanitarfuchs.de, verwenden Sie bitte folgenden Code:" + totp);
|
||||||
}
|
}
|
||||||
return Ok(); // always OK to prevent enumeration
|
return Ok(); // always OK to prevent enumeration
|
||||||
@@ -226,10 +226,10 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
var row = await _intranet.GetUserAccountByEmailAsync(email, includePassword: true);
|
var row = await _intranet.GetUserAccountByEmailAsync(email, includePassword: true);
|
||||||
if (row != null && row.nz("email").Length > 5)
|
if (row != null && row.nz("email").Length > 5)
|
||||||
{
|
{
|
||||||
await FuchsFdsEmail.SendEmail("pw_" + row.nz("email"),
|
await _comService.SendEmailAsync("pw_" + row.nz("email"),
|
||||||
"sanitaerfuchs.de Intranet Passwort",
|
"sanitaerfuchs.de Intranet Passwort",
|
||||||
$"<p>Guten Tag {row.nz("firstname")} {row.nz("name")},<br/>Ihr Passwort: {HttpUtility.HtmlEncode(row.nz("password"))}</p>",
|
$"<p>Guten Tag {row.nz("firstname")} {row.nz("name")},<br/>Ihr Passwort: {HttpUtility.HtmlEncode(row.nz("password"))}</p>",
|
||||||
row.nz("email"), $"{row.nz("firstname")} {row.nz("name")}", null, _intranet);
|
row.nz("email"), $"{row.nz("firstname")} {row.nz("name")}", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
@@ -243,11 +243,9 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
var row = await _intranet.GetUserAccountByEmailAsync(UserIdent.Email, includePassword: true);
|
var row = await _intranet.GetUserAccountByEmailAsync(UserIdent.Email, includePassword: true);
|
||||||
if (row != null && row.nz("mobile").Length > 5 && !Request.Host.Host.Contains("localhost"))
|
if (row != null && row.nz("mobile").Length > 5 && !Request.Host.Host.Contains("localhost"))
|
||||||
{
|
{
|
||||||
OCORE.sms.SMS77.Settings.APIKey = _intranet.Intranet__SMS_API_key;
|
|
||||||
using var sms2 = new OCORE.sms.SMS77.SMS("ProcessWeb");
|
|
||||||
string totp2 = OCORE.security.TFA.generateTotp_3h(_intranet.Intranet__TOTPsharedsecret_base + "3MDR");
|
string totp2 = OCORE.security.TFA.generateTotp_3h(_intranet.Intranet__TOTPsharedsecret_base + "3MDR");
|
||||||
if (long.TryParse(row.nz("mobile").Replace("+", "00").Replace(" ", ""), out long mob2))
|
await _comService.SendSmsAsync(row.nz("mobile"),
|
||||||
sms2.SendSMS_sync(mob2, "Zur Bestätigung der Passwortänderung auf sanitarfuchs.de: " + totp2);
|
"Zur Bestätigung der Passwortänderung auf sanitarfuchs.de: " + totp2);
|
||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -12,11 +12,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\MT940Parser\MT940Parser\MT940Parser.csproj" />
|
<ProjectReference Include="..\..\..\WebProjectComponents\MT940Parser\MT940Parser\MT940Parser.csproj" />
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\OCORE_web\OCORE_web.csproj" />
|
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\OCORE\OCORE\OCORE.csproj" />
|
|
||||||
<ProjectReference Include="..\Fuchs_DataService\Fuchs_DataService.csproj" />
|
<ProjectReference Include="..\Fuchs_DataService\Fuchs_DataService.csproj" />
|
||||||
<ProjectReference Include="..\MFR_RESTClient\MFR_RESTClient.csproj" />
|
<ProjectReference Include="..\MFR_RESTClient\MFR_RESTClient.csproj" />
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\OCORE_web_pdf\OCORE_web_pdf.csproj" />
|
<ProjectReference Include="..\OCORE\OCORE\OCORE.csproj" />
|
||||||
|
<ProjectReference Include="..\OCORE_web\OCORE_web\OCORE_web.csproj" />
|
||||||
|
<ProjectReference Include="..\OCORE_web_pdf\OCORE_web_pdf.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="code\7z.dll" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="code\7z.dll" CopyToOutputDirectory="PreserveNewest" />
|
||||||
@@ -40,10 +40,10 @@
|
|||||||
<PackageReference Include="MimeKit" Version="4.16.0" />
|
<PackageReference Include="MimeKit" Version="4.16.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<!-- New packages (needed for .NET 10) -->
|
<!-- New packages (needed for .NET 10) -->
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.6" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="10.0.6" />
|
<PackageReference Include="System.Drawing.Common" Version="10.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="App_Data\cache\" />
|
<Folder Include="App_Data\cache\" />
|
||||||
|
|||||||
+4
-3
@@ -71,9 +71,10 @@ public class Program
|
|||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
// Email service
|
// Communication service (email + SMS via ProcessWeb Mailer API)
|
||||||
builder.Services.Configure<FuchsEmailSettings>(builder.Configuration.GetSection("Fuchs:Email"));
|
builder.Services.Configure<ProcessWebComSettings>(builder.Configuration.GetSection("Fuchs:Mailer"));
|
||||||
builder.Services.AddScoped<IEmailService, FuchsEmailService>();
|
builder.Services.AddHttpClient("ProcessWebMailer");
|
||||||
|
builder.Services.AddScoped<IComService, ProcessWebComService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureApp(WebApplication app)
|
private static void ConfigureApp(WebApplication app)
|
||||||
|
|||||||
@@ -1,197 +0,0 @@
|
|||||||
using Fuchs.intranet;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using MimeKit;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.SQL.sql;
|
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Email service implementation. Replaces the static <c>FuchsFdsEmail</c> class.
|
|
||||||
/// Reads settings from <c>IOptions<FuchsEmailSettings></c> (appsettings.json)
|
|
||||||
/// instead of <c>System.Configuration.ConfigurationManager</c>.
|
|
||||||
/// </summary>
|
|
||||||
public class FuchsEmailService : IEmailService
|
|
||||||
{
|
|
||||||
private readonly ILogger<FuchsEmailService> _logger;
|
|
||||||
private readonly Fuchs_intranet _intranet;
|
|
||||||
private readonly FuchsEmailSettings _emailSettings;
|
|
||||||
private OCORE.email.EmailServerSettings? _cachedSettings;
|
|
||||||
|
|
||||||
private const string ReplyToName = "Sebastian Fuchs - Bad und Heizung";
|
|
||||||
private const string ReplyToAddress = "info@sanitaerfuchs.de";
|
|
||||||
private const string SignatureIntro =
|
|
||||||
"<p> </p><p style=\"margin:24px 0 16px 0;line-height:140%;\">" +
|
|
||||||
"Herzliche Gr\u00fc\u00dfe aus D\u00fcsseldorf-Bilk<br/>" +
|
|
||||||
"Ihr Team der Firma Sebastian Fuchs</p>";
|
|
||||||
|
|
||||||
public FuchsEmailService(
|
|
||||||
ILogger<FuchsEmailService> logger,
|
|
||||||
Fuchs_intranet intranet,
|
|
||||||
IOptions<FuchsEmailSettings> emailSettings)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_intranet = intranet;
|
|
||||||
_emailSettings = emailSettings.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SendEmailAsync(string reference, string subject, string html,
|
|
||||||
string email, string name, Dictionary<string, byte[]>? attachments)
|
|
||||||
{
|
|
||||||
var errors = new List<string>();
|
|
||||||
string guid = "";
|
|
||||||
string config = "";
|
|
||||||
DateTime sent = default;
|
|
||||||
|
|
||||||
if (!IsValidEmail(email))
|
|
||||||
errors.Add("Die Email-Adresse ist nicht g\u00fcltig.");
|
|
||||||
if (string.IsNullOrEmpty(html))
|
|
||||||
errors.Add("Bitte geben Sie eine Nachricht ein.");
|
|
||||||
|
|
||||||
if (errors.Count == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var settings = GetEmailSettings();
|
|
||||||
string body = html + BuildSignature();
|
|
||||||
|
|
||||||
// Development redirect: replace To with DevRedirectAddress, suppress CC/BCC
|
|
||||||
string devRedirect = _emailSettings.DevRedirectAddress;
|
|
||||||
bool isDevRedirect = !string.IsNullOrWhiteSpace(devRedirect);
|
|
||||||
if (isDevRedirect)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("DEV redirect: email to '{Original}' redirected to '{Redirect}'", email, devRedirect);
|
|
||||||
email = devRedirect;
|
|
||||||
name = "Dev Redirect";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
var msg = new OCORE.email.Email(
|
|
||||||
Mode: OCORE.email.EmailMode.DirectMode,
|
|
||||||
type: settings.type)
|
|
||||||
{
|
|
||||||
EmailSettings = settings,
|
|
||||||
Subject = subject
|
|
||||||
};
|
|
||||||
msg.AddTo(email, name);
|
|
||||||
msg.AddReplyTo(new MailboxAddress(ReplyToName, ReplyToAddress));
|
|
||||||
msg.SetBody(body);
|
|
||||||
if (attachments != null)
|
|
||||||
foreach (var kv in attachments)
|
|
||||||
msg.AttachFile(filecontent: kv.Value, kv.Key);
|
|
||||||
|
|
||||||
var result = await msg.SendAsync(
|
|
||||||
(dref, ex) => _logger.LogError(ex, "Email send error {Reference}", dref));
|
|
||||||
|
|
||||||
guid = msg.MessageId ?? "";
|
|
||||||
config = msg.EmailConfig_serialized;
|
|
||||||
sent = result.Timestamp;
|
|
||||||
errors.AddRange(result.ErrorMessages);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var main = _emailSettings.Main;
|
|
||||||
string host = !string.IsNullOrEmpty(main.Host) ? main.Host : _emailSettings.SmtpHost;
|
|
||||||
string user = !string.IsNullOrEmpty(main.Username) ? main.Username : _emailSettings.SmtpUser;
|
|
||||||
string pass = !string.IsNullOrEmpty(main.Password) ? main.Password : _emailSettings.SmtpPass;
|
|
||||||
string from = !string.IsNullOrEmpty(main.From) ? main.From
|
|
||||||
: (!string.IsNullOrEmpty(_emailSettings.SmtpFrom) ? _emailSettings.SmtpFrom : user);
|
|
||||||
string fromName = !string.IsNullOrEmpty(main.Alias) ? main.Alias : _emailSettings.SmtpFromName;
|
|
||||||
int port = main.Port > 0 ? main.Port : _emailSettings.SmtpPort;
|
|
||||||
|
|
||||||
var message = new MimeMessage();
|
|
||||||
message.From.Add(new MailboxAddress(fromName, from));
|
|
||||||
message.To.Add(new MailboxAddress(name, email));
|
|
||||||
message.ReplyTo.Add(new MailboxAddress(ReplyToName, ReplyToAddress));
|
|
||||||
message.Subject = subject;
|
|
||||||
|
|
||||||
var builder = new BodyBuilder { HtmlBody = body };
|
|
||||||
if (attachments != null)
|
|
||||||
foreach (var kv in attachments)
|
|
||||||
builder.Attachments.Add(kv.Key, kv.Value);
|
|
||||||
message.Body = builder.ToMessageBody();
|
|
||||||
|
|
||||||
using var client = new MailKit.Net.Smtp.SmtpClient();
|
|
||||||
await client.ConnectAsync(host, port, MailKit.Security.SecureSocketOptions.Auto);
|
|
||||||
if (!string.IsNullOrEmpty(user))
|
|
||||||
await client.AuthenticateAsync(user, pass);
|
|
||||||
await client.SendAsync(message);
|
|
||||||
await client.DisconnectAsync(true);
|
|
||||||
|
|
||||||
guid = message.MessageId!;
|
|
||||||
sent = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errors.Add("Beim Versenden ist ein Fehler aufgetreten.");
|
|
||||||
_logger.LogError(ex, "SendEmailAsync failed for {Reference}", reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.Count > 0)
|
|
||||||
_logger.LogWarning("SendEmailAsync errors for {Reference}: {Errors}", reference, errors);
|
|
||||||
|
|
||||||
// SQL audit log
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = new List<SqlParameter>
|
|
||||||
{
|
|
||||||
SQL_VarChar("@Ref", reference),
|
|
||||||
SQL_VarChar("@guid", guid),
|
|
||||||
SQL_DateTime("@DateSent", sent == default ? DBNull.Value : (object)sent),
|
|
||||||
SQL_NVarChar("@config", config, dbNull_IfEmpty: true),
|
|
||||||
SQL_Bit("@success", errors.Count == 0),
|
|
||||||
SQL_NVarChar("@log", JsonConvert.SerializeObject(errors))
|
|
||||||
};
|
|
||||||
await setSQLValue_async(
|
|
||||||
"EXECUTE [dbo].[fds__logEmail] @Ref, @guid, @DateSent, @config, @success, @log;",
|
|
||||||
_intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: _intranet.GetDbSecurity());
|
|
||||||
}
|
|
||||||
catch (Exception logEx)
|
|
||||||
{
|
|
||||||
_logger.LogError(logEx, "Failed to log email audit for {Reference}", reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Count == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OCORE.email.EmailServerSettings? GetEmailSettings()
|
|
||||||
{
|
|
||||||
if (_cachedSettings != null) return _cachedSettings;
|
|
||||||
|
|
||||||
var main = _emailSettings.Main;
|
|
||||||
if (string.IsNullOrWhiteSpace(main.Host)) return null;
|
|
||||||
|
|
||||||
_cachedSettings = new OCORE.email.EmailServerSettings(main.Type, main.ToOcoreJson());
|
|
||||||
return _cachedSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildSignature()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string sigPath = Path.Combine(AppContext.BaseDirectory,
|
|
||||||
"email_signature", "sanitaerfuchs_email_signature.txt");
|
|
||||||
if (File.Exists(sigPath))
|
|
||||||
return SignatureIntro + File.ReadAllText(sigPath);
|
|
||||||
}
|
|
||||||
catch { /* signature is optional */ }
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsValidEmail(string email)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var a = new System.Net.Mail.MailAddress(email);
|
|
||||||
return string.Equals(a.Address, email, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
catch { return false; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
namespace Fuchs.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SMTP / email server settings bound from appsettings.json → "Fuchs:Email" section.
|
|
||||||
/// </summary>
|
|
||||||
public class FuchsEmailSettings
|
|
||||||
{
|
|
||||||
/// <summary>Main account for outgoing customer emails (anfrage@sanitaerfuchs.de).</summary>
|
|
||||||
public SmtpAccountSettings Main { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>FDS/invoicing account (rechnungen@sanitaerfuchs.de).</summary>
|
|
||||||
public SmtpAccountSettings Fds { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>Internal OCORE service account used for system notifications.</summary>
|
|
||||||
public SmtpAccountSettings Service { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>Comma-separated list of addresses used as test recipients.</summary>
|
|
||||||
public string TestAddresses { get; set; } = "";
|
|
||||||
|
|
||||||
// ── MailKit fallback settings (used when Main is not configured) ──
|
|
||||||
public string SmtpHost { get; set; } = "";
|
|
||||||
public int SmtpPort { get; set; } = 587;
|
|
||||||
public string SmtpUser { get; set; } = "";
|
|
||||||
public string SmtpPass { get; set; } = "";
|
|
||||||
public string SmtpFrom { get; set; } = "";
|
|
||||||
public string SmtpFromName { get; set; } = "SanitärFuchs";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Development only: when set, all outgoing emails are redirected to this address;
|
|
||||||
/// CC and BCC are cleared. Leave empty in production.
|
|
||||||
/// </summary>
|
|
||||||
public string DevRedirectAddress { get; set; } = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for sending emails with optional attachments and audit logging.
|
/// Abstraction for outbound communication: email and SMS.
|
||||||
|
/// Backed by the ProcessWeb Mailer API (POST /api/mailer?fn=push_com).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEmailService
|
public interface IComService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends an email and logs the result to the database.
|
/// Sends an email and logs the result to the database.
|
||||||
@@ -14,7 +15,15 @@ public interface IEmailService
|
|||||||
/// <param name="email">Recipient email address.</param>
|
/// <param name="email">Recipient email address.</param>
|
||||||
/// <param name="name">Recipient display name.</param>
|
/// <param name="name">Recipient display name.</param>
|
||||||
/// <param name="attachments">Optional file attachments (filename → content).</param>
|
/// <param name="attachments">Optional file attachments (filename → content).</param>
|
||||||
/// <returns><c>true</c> when the email was sent successfully.</returns>
|
/// <returns><c>true</c> when the email was accepted successfully.</returns>
|
||||||
Task<bool> SendEmailAsync(string reference, string subject, string html,
|
Task<bool> SendEmailAsync(string reference, string subject, string html,
|
||||||
string email, string name, Dictionary<string, byte[]>? attachments);
|
string email, string name, Dictionary<string, byte[]>? attachments = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an SMS message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mobile">Recipient mobile number (E.164 or local format).</param>
|
||||||
|
/// <param name="message">Text message body.</param>
|
||||||
|
/// <returns><c>true</c> when the SMS was accepted successfully.</returns>
|
||||||
|
Task<bool> SendSmsAsync(string mobile, string message);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using Fuchs.intranet;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using OCORE.SQL;
|
||||||
|
using static OCORE.SQL.sql;
|
||||||
|
|
||||||
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Outbound communication service backed by the ProcessWeb Mailer API
|
||||||
|
/// (POST https://api.processweb.de/api/mailer?fn=push_com).
|
||||||
|
/// When <c>ProcessWebComSettings.Enabled</c> is <c>false</c> the service
|
||||||
|
/// only logs the intended communication without calling the API.
|
||||||
|
/// </summary>
|
||||||
|
public class ProcessWebComService : IComService
|
||||||
|
{
|
||||||
|
private readonly ILogger<ProcessWebComService> _logger;
|
||||||
|
private readonly Fuchs_intranet _intranet;
|
||||||
|
private readonly ProcessWebComSettings _settings;
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
private const string SignatureIntro =
|
||||||
|
"<p> </p><p style=\"margin:24px 0 16px 0;line-height:140%;\">" +
|
||||||
|
"Herzliche Grüße aus Düsseldorf-Bilk<br/>" +
|
||||||
|
"Ihr Team der Firma Sebastian Fuchs</p>";
|
||||||
|
|
||||||
|
public ProcessWebComService(
|
||||||
|
ILogger<ProcessWebComService> logger,
|
||||||
|
Fuchs_intranet intranet,
|
||||||
|
IOptions<ProcessWebComSettings> settings,
|
||||||
|
IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_intranet = intranet;
|
||||||
|
_settings = settings.Value;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendEmailAsync(string reference, string subject, string html,
|
||||||
|
string email, string name, Dictionary<string, byte[]>? attachments = null)
|
||||||
|
{
|
||||||
|
if (!IsValidEmail(email))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("SendEmailAsync: invalid email address '{Email}' for ref {Reference}", email, reference);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string body = html + BuildSignature();
|
||||||
|
|
||||||
|
if (!_settings.Enabled)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"[ComService DISABLED] Would send email ref={Reference} subject='{Subject}' to={Email}",
|
||||||
|
reference, subject, email);
|
||||||
|
await WriteAuditLogAsync(reference, "", "", default, false, ["Service disabled – email not sent"]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
string messageId = "";
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
comType = "email",
|
||||||
|
recipient = email,
|
||||||
|
subject,
|
||||||
|
body
|
||||||
|
};
|
||||||
|
|
||||||
|
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
messageId = Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errors.Add($"API error: {responseBody}");
|
||||||
|
_logger.LogWarning("SendEmailAsync API error for {Reference}: {Body}", reference, responseBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errors.Add("Beim Versenden ist ein Fehler aufgetreten.");
|
||||||
|
_logger.LogError(ex, "SendEmailAsync failed for {Reference}", reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteAuditLogAsync(reference, messageId, "", success ? DateTime.UtcNow : default, success, errors);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendSmsAsync(string mobile, string message)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mobile))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("SendSmsAsync: empty mobile number");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_settings.Enabled)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"[ComService DISABLED] Would send SMS to={Mobile} message='{Message}'",
|
||||||
|
mobile, message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
comType = "sms",
|
||||||
|
recipient = mobile,
|
||||||
|
body = message
|
||||||
|
};
|
||||||
|
|
||||||
|
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||||
|
if (ok) return true;
|
||||||
|
|
||||||
|
_logger.LogWarning("SendSmsAsync API error for {Mobile}: {Body}", mobile, responseBody);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "SendSmsAsync failed for {Mobile}", mobile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Private helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private async Task<(bool ok, string body)> PostToApiAsync(string fn, object payload)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
||||||
|
var json = JsonConvert.SerializeObject(payload);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
string credentials = Convert.ToBase64String(
|
||||||
|
Encoding.UTF8.GetBytes($"{_settings.AccountId}:{_settings.Token}"));
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new AuthenticationHeaderValue("Basic", credentials);
|
||||||
|
|
||||||
|
var response = await client.PostAsync($"{_settings.BaseUrl}/api/mailer?fn={fn}", content);
|
||||||
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
return (response.IsSuccessStatusCode, responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WriteAuditLogAsync(string reference, string guid, string config,
|
||||||
|
DateTime sent, bool success, IEnumerable<string> errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("@Ref", reference),
|
||||||
|
SQL_VarChar("@guid", guid),
|
||||||
|
SQL_DateTime("@DateSent", sent == default ? DBNull.Value : (object)sent),
|
||||||
|
SQL_NVarChar("@config", config, dbNull_IfEmpty: true),
|
||||||
|
SQL_Bit("@success", success),
|
||||||
|
SQL_NVarChar("@log", JsonConvert.SerializeObject(errors.ToList()))
|
||||||
|
};
|
||||||
|
await setSQLValue_async(
|
||||||
|
"EXECUTE [dbo].[fds__logEmail] @Ref, @guid, @DateSent, @config, @success, @log;",
|
||||||
|
_intranet.Intranet__SQLConnectionString, pl,
|
||||||
|
Security: _intranet.GetDbSecurity());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to write audit log for {Reference}", reference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildSignature()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string sigPath = Path.Combine(AppContext.BaseDirectory,
|
||||||
|
"email_signature", "sanitaerfuchs_email_signature.txt");
|
||||||
|
if (File.Exists(sigPath))
|
||||||
|
return SignatureIntro + File.ReadAllText(sigPath);
|
||||||
|
}
|
||||||
|
catch { /* signature is optional */ }
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidEmail(string email)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var a = new System.Net.Mail.MailAddress(email);
|
||||||
|
return string.Equals(a.Address, email, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Settings for the ProcessWeb Mailer API, bound from appsettings.json → "Fuchs:Mailer".
|
||||||
|
/// </summary>
|
||||||
|
public class ProcessWebComSettings
|
||||||
|
{
|
||||||
|
/// <summary>Base URL of the ProcessWeb API (e.g. "https://api.processweb.de").</summary>
|
||||||
|
public string BaseUrl { get; set; } = "https://api.processweb.de";
|
||||||
|
|
||||||
|
/// <summary>Account ID used for HTTP Basic authentication.</summary>
|
||||||
|
public string AccountId { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>API token used for HTTP Basic authentication.</summary>
|
||||||
|
public string Token { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When <c>false</c> (default) the service is disabled and only logs the
|
||||||
|
/// intended communication without actually calling the API.
|
||||||
|
/// Set to <c>true</c> to enable live sending.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; } = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace OCORE_web.Secrets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Placeholder for secret management extension methods.
|
||||||
|
/// Replace with a real Azure Key Vault / DPAPI implementation when ready.
|
||||||
|
/// </summary>
|
||||||
|
public static class SecretManagementExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No-op stub. Wire up real secret management (e.g. Azure Key Vault) here.
|
||||||
|
/// </summary>
|
||||||
|
public static WebApplicationBuilder AddSecretManagement(this WebApplicationBuilder builder)
|
||||||
|
=> builder;
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SMTP account configuration for a single email account.
|
|
||||||
/// Property names match the OCORE EmailServerSettings JSON format (lowercase).
|
|
||||||
/// </summary>
|
|
||||||
public class SmtpAccountSettings
|
|
||||||
{
|
|
||||||
[JsonProperty("alias")]
|
|
||||||
public string Alias { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("to")]
|
|
||||||
public string To { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("from")]
|
|
||||||
public string From { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("bcc")]
|
|
||||||
public string Bcc { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("host")]
|
|
||||||
public string Host { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("port")]
|
|
||||||
public int Port { get; set; } = 587;
|
|
||||||
|
|
||||||
[JsonProperty("security")]
|
|
||||||
public string Security { get; set; } = "StartTls";
|
|
||||||
|
|
||||||
[JsonProperty("username")]
|
|
||||||
public string Username { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("password")]
|
|
||||||
public string Password { get; set; } = "";
|
|
||||||
|
|
||||||
[JsonProperty("type")]
|
|
||||||
public string Type { get; set; } = "smtp";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes this instance back to the JSON string format expected by
|
|
||||||
/// <c>OCORE.email.EmailServerSettings(type, raw)</c>.
|
|
||||||
/// </summary>
|
|
||||||
public string ToOcoreJson() => JsonConvert.SerializeObject(this);
|
|
||||||
}
|
|
||||||
+6
-38
@@ -8,9 +8,7 @@
|
|||||||
"ConnectionStrings--ocms-ConnectionString",
|
"ConnectionStrings--ocms-ConnectionString",
|
||||||
"ConnectionStrings--fuchs-fds-ConnectionString",
|
"ConnectionStrings--fuchs-fds-ConnectionString",
|
||||||
"Fuchs--SMS-APIKey",
|
"Fuchs--SMS-APIKey",
|
||||||
"Fuchs--Email--Main--password",
|
"Fuchs--Mailer--Token",
|
||||||
"Fuchs--Email--Fds--password",
|
|
||||||
"Fuchs--Email--Service--password",
|
|
||||||
"Fuchs--fuchs-captcha-TOTP",
|
"Fuchs--fuchs-captcha-TOTP",
|
||||||
"Fuchs--fuchs-intranet-TOTP"
|
"Fuchs--fuchs-intranet-TOTP"
|
||||||
]
|
]
|
||||||
@@ -35,41 +33,11 @@
|
|||||||
"fuchs_captcha_TOTP": "MANAGED_BY_KEYVAULT",
|
"fuchs_captcha_TOTP": "MANAGED_BY_KEYVAULT",
|
||||||
"fuchs_intranet_TOTP": "MANAGED_BY_KEYVAULT",
|
"fuchs_intranet_TOTP": "MANAGED_BY_KEYVAULT",
|
||||||
"SMS_APIKey": "MANAGED_BY_KEYVAULT",
|
"SMS_APIKey": "MANAGED_BY_KEYVAULT",
|
||||||
"Email": {
|
"Mailer": {
|
||||||
"Main": {
|
"BaseUrl": "https://api.processweb.de",
|
||||||
"alias": "Sebastian Fuchs - Bad und Heizung",
|
"AccountId": "",
|
||||||
"to": "anfrage@sanitaerfuchs.de",
|
"Token": "MANAGED_BY_KEYVAULT",
|
||||||
"from": "anfrage@sanitaerfuchs.de",
|
"Enabled": false
|
||||||
"bcc": "info@processweb.de",
|
|
||||||
"host": "smtp.office365.com",
|
|
||||||
"port": 587,
|
|
||||||
"security": "StartTls",
|
|
||||||
"username": "anfrage@sanitaerfuchs.de",
|
|
||||||
"password": "MANAGED_BY_KEYVAULT"
|
|
||||||
},
|
|
||||||
"Fds": {
|
|
||||||
"alias": "Sebastian Fuchs - Bad und Heizung",
|
|
||||||
"to": "",
|
|
||||||
"from": "rechnungen@sanitaerfuchs.de",
|
|
||||||
"bcc": "",
|
|
||||||
"host": "smtp.office365.com",
|
|
||||||
"port": 587,
|
|
||||||
"security": "StartTls",
|
|
||||||
"username": "rechnungen@sanitaerfuchs.de",
|
|
||||||
"password": "MANAGED_BY_KEYVAULT"
|
|
||||||
},
|
|
||||||
"Service": {
|
|
||||||
"alias": "ProcessWeb Service",
|
|
||||||
"to": "",
|
|
||||||
"from": "service@emails.processweb.de",
|
|
||||||
"bcc": "",
|
|
||||||
"host": "emails.processweb.de",
|
|
||||||
"port": 587,
|
|
||||||
"security": "StartTls",
|
|
||||||
"username": "service@emails.processweb.de",
|
|
||||||
"password": "MANAGED_BY_KEYVAULT"
|
|
||||||
},
|
|
||||||
"TestAddresses": "st.ott@web.de,info@processweb.de"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
using Cfg = System.Configuration.ConfigurationManager;
|
|
||||||
using Fuchs.intranet;
|
|
||||||
using MimeKit;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.SQL.sql;
|
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Email sending helper for Fuchs intranet. Full port of fuchs_fds_email.vb.
|
|
||||||
/// Uses OCORE.email.Emailcommons.SendEmail_async when settings are available,
|
|
||||||
/// falls back to MailKit direct send otherwise.
|
|
||||||
/// </summary>
|
|
||||||
public static class FuchsFdsEmail
|
|
||||||
{
|
|
||||||
private static OCORE.email.EmailServerSettings? _settings;
|
|
||||||
|
|
||||||
private static OCORE.email.EmailServerSettings? GetEmailSettings()
|
|
||||||
{
|
|
||||||
if (_settings != null) return _settings;
|
|
||||||
string raw = Cfg.AppSettings["FDS_EmailSettings"] ?? "";
|
|
||||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
|
||||||
|
|
||||||
// The serialised string is JSON containing a "type" field
|
|
||||||
string type = "smtp";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(raw);
|
|
||||||
if (dic?.TryGetValue("type", out var t) == true)
|
|
||||||
type = t?.ToString() ?? "smtp";
|
|
||||||
}
|
|
||||||
catch { /* keep default type */ }
|
|
||||||
|
|
||||||
_settings = new OCORE.email.EmailServerSettings(type, raw);
|
|
||||||
return _settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const string ReplyToName = "Sebastian Fuchs - Bad und Heizung";
|
|
||||||
private const string ReplyToAddress = "info@sanitaerfuchs.de";
|
|
||||||
private const string SignatureIntro =
|
|
||||||
"<p> </p><p style=\"margin:24px 0 16px 0;line-height:140%;\">" +
|
|
||||||
"Herzliche Gr\u00fc\u00dfe aus D\u00fcsseldorf-Bilk<br/>" +
|
|
||||||
"Ihr Team der Firma Sebastian Fuchs</p>";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an email and logs the result to the Fuchs FDS database.
|
|
||||||
/// Returns true on success.
|
|
||||||
/// </summary>
|
|
||||||
public static async Task<bool> SendEmail(
|
|
||||||
string @ref,
|
|
||||||
string subject,
|
|
||||||
string html,
|
|
||||||
string email,
|
|
||||||
string name,
|
|
||||||
Dictionary<string, byte[]>? files,
|
|
||||||
Fuchs_intranet intranet)
|
|
||||||
{
|
|
||||||
var errors = new List<string>();
|
|
||||||
string guid = "";
|
|
||||||
string config = "";
|
|
||||||
DateTime sent = default;
|
|
||||||
|
|
||||||
if (!IsValidEmail(email))
|
|
||||||
errors.Add("Die Email-Adresse ist nicht g\u00fcltig.");
|
|
||||||
if (string.IsNullOrEmpty(html))
|
|
||||||
errors.Add("Bitte geben Sie eine Nachricht ein.");
|
|
||||||
|
|
||||||
if (errors.Count == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var settings = GetEmailSettings();
|
|
||||||
string body = html + BuildSignature();
|
|
||||||
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
// ── OCORE path ─────────────────────────────────────────
|
|
||||||
var msg = new OCORE.email.Email(
|
|
||||||
Mode: OCORE.email.EmailMode.DirectMode,
|
|
||||||
type: settings.type)
|
|
||||||
{
|
|
||||||
EmailSettings = settings,
|
|
||||||
Subject = subject
|
|
||||||
};
|
|
||||||
msg.AddTo(email, name);
|
|
||||||
msg.AddReplyTo(new MailboxAddress(ReplyToName, ReplyToAddress));
|
|
||||||
msg.SetBody(body);
|
|
||||||
if (files != null)
|
|
||||||
foreach (var kv in files)
|
|
||||||
msg.AttachFile(filecontent: kv.Value, kv.Key);
|
|
||||||
|
|
||||||
var result = await msg.SendAsync(
|
|
||||||
(dref, ex) => intranet.debug_log(
|
|
||||||
$"FuchsFdsEmail.SendEmail {dref}", ex));
|
|
||||||
|
|
||||||
guid = msg.MessageId ?? "";
|
|
||||||
config = msg.EmailConfig_serialized;
|
|
||||||
sent = result.Timestamp;
|
|
||||||
errors.AddRange(result.ErrorMessages);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ── MailKit fallback (app settings) ────────────────────
|
|
||||||
string host = Cfg.AppSettings["smtp_host"] ?? "";
|
|
||||||
string user = Cfg.AppSettings["smtp_user"] ?? "";
|
|
||||||
string pass = Cfg.AppSettings["smtp_pass"] ?? "";
|
|
||||||
string from = Cfg.AppSettings["smtp_from"] ?? user;
|
|
||||||
string fromName = Cfg.AppSettings["smtp_fromname"] ?? "Sanit\u00e4rFuchs";
|
|
||||||
int port = int.TryParse(Cfg.AppSettings["smtp_port"], out int p) ? p : 587;
|
|
||||||
|
|
||||||
var message = new MimeMessage();
|
|
||||||
message.From.Add(new MailboxAddress(fromName, from));
|
|
||||||
message.To.Add(new MailboxAddress(name, email));
|
|
||||||
message.ReplyTo.Add(new MailboxAddress(ReplyToName, ReplyToAddress));
|
|
||||||
message.Subject = subject;
|
|
||||||
|
|
||||||
var builder = new BodyBuilder { HtmlBody = body };
|
|
||||||
if (files != null)
|
|
||||||
foreach (var kv in files)
|
|
||||||
builder.Attachments.Add(kv.Key, kv.Value);
|
|
||||||
message.Body = builder.ToMessageBody();
|
|
||||||
|
|
||||||
using var client = new MailKit.Net.Smtp.SmtpClient();
|
|
||||||
await client.ConnectAsync(host, port,
|
|
||||||
MailKit.Security.SecureSocketOptions.Auto);
|
|
||||||
if (!string.IsNullOrEmpty(user))
|
|
||||||
await client.AuthenticateAsync(user, pass);
|
|
||||||
await client.SendAsync(message);
|
|
||||||
await client.DisconnectAsync(true);
|
|
||||||
|
|
||||||
guid = message.MessageId!;
|
|
||||||
sent = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errors.Add("Beim Versenden ist ein Fehler aufgetreten.");
|
|
||||||
intranet.debug_log("FuchsFdsEmail.SendEmail inner", ex, data: errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.Count > 0)
|
|
||||||
intranet.debug_log("FuchsFdsEmail.SendEmail",
|
|
||||||
data: new { @ref, email, errors });
|
|
||||||
|
|
||||||
// ── SQL audit log ──────────────────────────────────────────────────
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = new List<SqlParameter>
|
|
||||||
{
|
|
||||||
SQL_VarChar("@Ref", @ref),
|
|
||||||
SQL_VarChar("@guid", guid),
|
|
||||||
SQL_DateTime("@DateSent", sent == default ? DBNull.Value : (object)sent),
|
|
||||||
SQL_NVarChar("@config", config, dbNull_IfEmpty: true),
|
|
||||||
SQL_Bit("@success", errors.Count == 0),
|
|
||||||
SQL_NVarChar("@log", JsonConvert.SerializeObject(errors))
|
|
||||||
};
|
|
||||||
await setSQLValue_async(
|
|
||||||
"EXECUTE [dbo].[fds__logEmail] @Ref, @guid, @DateSent, @config, @success, @log;",
|
|
||||||
intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: intranet.GetDbSecurity());
|
|
||||||
}
|
|
||||||
catch (Exception logEx)
|
|
||||||
{
|
|
||||||
intranet.debug_log("FuchsFdsEmail.SendEmail log", logEx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Count == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
||||||
private static string BuildSignature()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string sigPath = Path.Combine(AppContext.BaseDirectory,
|
|
||||||
"email_signature", "sanitaerfuchs_email_signature.txt");
|
|
||||||
if (File.Exists(sigPath))
|
|
||||||
return SignatureIntro + File.ReadAllText(sigPath);
|
|
||||||
}
|
|
||||||
catch { /* signature is optional */ }
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsValidEmail(string email)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var a = new System.Net.Mail.MailAddress(email);
|
|
||||||
return string.Equals(a.Address, email, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
catch { return false; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "fuchs",
|
"name": "fuchs",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MFR_RESTClient\MFR_RESTClient.csproj" />
|
<ProjectReference Include="..\MFR_RESTClient\MFR_RESTClient.csproj" />
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\OCORE_web\OCORE_web.csproj" />
|
<ProjectReference Include="..\OCORE\OCORE\OCORE.csproj" />
|
||||||
<ProjectReference Include="..\..\..\WebProjectComponents\OCORE\OCORE\OCORE.csproj" />
|
<ProjectReference Include="..\OCORE_web\OCORE_web\OCORE_web.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="7z.dll">
|
<Content Include="7z.dll">
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.2.24" />
|
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.2.24" />
|
||||||
<PackageReference Include="Topshelf" Version="4.3.0" />
|
<PackageReference Include="Topshelf" Version="4.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.6" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+24
-16
@@ -12,22 +12,6 @@
|
|||||||
<BuildType Solution="db-dev.processweb.de|Any CPU" Project="Debug" />
|
<BuildType Solution="db-dev.processweb.de|Any CPU" Project="Debug" />
|
||||||
<BuildType Solution="server02.processweb.de|Any CPU" Project="Debug" />
|
<BuildType Solution="server02.processweb.de|Any CPU" Project="Debug" />
|
||||||
</Project>
|
</Project>
|
||||||
<Project Path="../../WebProjectComponents/OCORE/OCORE/OCORE.csproj">
|
|
||||||
<BuildType Solution="db-dev.processweb.de|*" Project="Debug" />
|
|
||||||
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
|
||||||
</Project>
|
|
||||||
<Project Path="../../WebProjectComponents/OCORE_Charting/OCORE_Charting.csproj">
|
|
||||||
<BuildType Solution="db-dev.processweb.de|*" Project="Debug" />
|
|
||||||
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
|
||||||
</Project>
|
|
||||||
<Project Path="../../WebProjectComponents/OCORE_web/OCORE_web.csproj">
|
|
||||||
<BuildType Solution="db-dev.processweb.de|*" Project="Debug" />
|
|
||||||
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
|
||||||
</Project>
|
|
||||||
<Project Path="../../WebProjectComponents/OCORE_web_pdf/OCORE_web_pdf.csproj">
|
|
||||||
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
|
||||||
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
|
||||||
</Project>
|
|
||||||
<Project Path="Fuchs.Tests/Fuchs.Tests.csproj">
|
<Project Path="Fuchs.Tests/Fuchs.Tests.csproj">
|
||||||
<BuildType Solution="db-dev.processweb.de|*" Project="Debug" />
|
<BuildType Solution="db-dev.processweb.de|*" Project="Debug" />
|
||||||
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
@@ -35,4 +19,28 @@
|
|||||||
<Project Path="Fuchs/Fuchs.csproj" />
|
<Project Path="Fuchs/Fuchs.csproj" />
|
||||||
<Project Path="Fuchs_DataService/Fuchs_DataService.csproj" />
|
<Project Path="Fuchs_DataService/Fuchs_DataService.csproj" />
|
||||||
<Project Path="MFR_RESTClient/MFR_RESTClient.csproj" />
|
<Project Path="MFR_RESTClient/MFR_RESTClient.csproj" />
|
||||||
|
<Project Path="OCORE/OCORE/OCORE.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="OCORE/OCORETests/OCORETests.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="OCORE_Charting/OCORE_Charting.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="OCORE_web/OCORE_web/OCORE_web.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="OCORE_web/OCORE_webTests/OCORE_webTests.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="OCORE_web_pdf/OCORE_web_pdf.csproj">
|
||||||
|
<BuildType Solution="db-dev.processweb.de|*" Project="Release" />
|
||||||
|
<BuildType Solution="server02.processweb.de|*" Project="Debug" />
|
||||||
|
</Project>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -26,21 +26,21 @@
|
|||||||
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
|
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
|
||||||
<PackageReference Include="System.Spatial" Version="5.8.5" />
|
<PackageReference Include="System.Spatial" Version="5.8.5" />
|
||||||
<!-- Updated packages -->
|
<!-- Updated packages -->
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" />
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="RestSharp" Version="114.0.0" />
|
<PackageReference Include="RestSharp" Version="114.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
|
||||||
<!-- New packages (replacements) -->
|
<!-- New packages (replacements) -->
|
||||||
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
|
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.6" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
|
||||||
<!-- Deprecated but kept for compatibility (review in follow-up) -->
|
<!-- Deprecated but kept for compatibility (review in follow-up) -->
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Abstractions" Version="8.17.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Abstractions" Version="8.18.0" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.3.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.3.0" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="8.17.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="8.18.0" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.18.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Submodule
+1
Submodule OCORE added at 9f4826767f
Submodule
+1
Submodule OCORE_Charting added at fcb8f090d4
Submodule
+1
Submodule OCORE_web added at fd5b23ec77
Submodule
+1
Submodule OCORE_web_pdf added at a94ddf90c3
Reference in New Issue
Block a user