Compare commits

..

12 Commits

Author SHA1 Message Date
f9a73fbe0c Refactor RecAction.ErrorAction to use enum type
Changed ErrorAction property in RecAction from string? to ErrorAction? enum for improved type safety. Added import for ReC.Domain.Constants to support the new type.
2025-12-12 13:02:17 +01:00
282ce3a0b7 Move ErrorAction enum to domain layer namespace
Changed ErrorAction enum namespace from ReC.Application.Common.Constants to ReC.Domain.Constants to better reflect its domain relevance. Removed the file's BOM. Enum members remain unchanged.
2025-12-12 13:00:24 +01:00
26f2da1313 Add ErrorAction enum for error handling actions
Introduced the ErrorAction enum in the ReC.Application.Common.Constants namespace with Stop and Continue values to standardize error response actions across the application.
2025-12-12 12:55:57 +01:00
bc700e2cd2 Refactor HTTP client name constant usage
Replaced the old HttpClientName constant in Constants.cs with a new Http.ClientName in the ReC.Application.Common.Constants namespace. Updated all references and using directives accordingly to improve code organization and maintainability.
2025-12-12 12:54:57 +01:00
ea5389df85 Remove Message property from OutRes and its EF mapping
The Message property was deleted from the OutRes class, and its corresponding Entity Framework mapping was removed from RecDbContext. This cleans up unused fields from both the model and database context configuration.
2025-12-12 12:51:43 +01:00
87e1bb9187 Refactor: centralize HTTP client naming with Constants
Introduced a Constants class to define a unique HttpClientName for HTTP client registration and usage. Updated DependencyInjection and InvokeRecActionCommandHandler to use this centralized name, improving consistency and reducing risk of name collisions.
2025-12-12 12:42:39 +01:00
68cc919bad Handle unsupported auth types with NotImplementedException
Throw NotImplementedException for unsupported authentication types
in InvokeRecActionCommandHandler, including details like ProfileId
and Id in the exception message for easier debugging. This prevents
silent failures when encountering unknown authentication methods.
2025-12-12 12:02:51 +01:00
374365d250 Refactor HttpClient usage and NTLM auth handling
Centralize HttpClient configuration using IHttpClientFactory with a named "Default" client. Move NTLM authentication logic from HttpClientHandler to request-level options, improving testability and aligning with .NET best practices.
2025-12-12 12:01:22 +01:00
8c79b3a156 Add support for multiple HTTP auth methods in REST actions
Expanded InvokeRecActionCommandHandler to support API Key, Bearer/JWT/OAuth2, Basic, and NTLM authentication schemes. Added necessary imports and logic for header/query manipulation and credential handling. Left placeholders for Digest, OAuth 1.0, and AWS Signature. Improves flexibility and robustness of outgoing HTTP requests.
2025-12-12 11:20:02 +01:00
0583d07f26 Add switch for endpoint auth types in action handler
Introduced a switch statement to handle various endpoint authentication types in InvokeRecActionCommandHandler. Cases for common auth methods have been added as placeholders, preparing the codebase for future implementation of specific authentication logic.
2025-12-12 10:50:49 +01:00
fd7744e94e Include EndpointAuth in RecActionView query results
Added .Include(act => act.EndpointAuth) to eagerly load the EndpointAuth navigation property when retrieving RecActionView entities. Also made a minor formatting adjustment to the check for empty action results.
2025-12-12 10:46:20 +01:00
a5aac1d0ec Add EndpointAuth navigation to RecActionView
Added EndpointAuth navigation property to RecActionView with [ForeignKey("EndpointAuthId")], enabling direct access to related authentication data via Entity Framework.
2025-12-12 10:34:26 +01:00
9 changed files with 101 additions and 12 deletions

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Constants;
public static class Http
{
public static readonly string ClientName = "HttpClient-" + Guid.NewGuid().ToString();
}

View File

@@ -1,10 +1,11 @@
using MediatR;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Constants;
using ReC.Application.Common.Options;
using System.Reflection;
using FluentValidation;
namespace ReC.Application;
@@ -35,7 +36,11 @@ public static class DependencyInjection
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
});
services.AddHttpClient();
services.AddHttpClient(Http.ClientName)
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseDefaultCredentials = false
});
return services;
}

View File

@@ -1,9 +1,13 @@
using MediatR;
using Microsoft.Extensions.Configuration;
using ReC.Application.Common;
using ReC.Application.Common.Constants;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.OutResults.Commands;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace ReC.Application.RecActions.Commands;
@@ -27,7 +31,8 @@ public class InvokeRecActionCommandHandler(
public async Task<bool> Handle(InvokeRecActionCommand request, CancellationToken cancel)
{
var action = request.Action;
using var http = clientFactory.CreateClient();
using var http = clientFactory.CreateClient(Http.ClientName);
if (action.RestType is null)
throw new DataIntegrityException(
@@ -40,7 +45,7 @@ public class InvokeRecActionCommandHandler(
.ToHttpMethod()
.ToHttpRequestMessage(action.EndpointUri);
if(action.Body is not null)
if (action.Body is not null)
{
using var reqBody = new StringContent(action.Body);
httpReq.Content = reqBody;
@@ -50,6 +55,70 @@ public class InvokeRecActionCommandHandler(
foreach (var header in action.Headers)
httpReq.Headers.Add(header.Key, header.Value);
switch (action.EndpointAuthType)
{
case "No Auth":
break;
case "API Key":
if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue)
{
if (action.EndpointAuthApiKeyAddTo == "Header")
{
httpReq.Headers.Add(apiKey, apiValue);
}
else // Defaults to Query String
{
var uriBuilder = new UriBuilder(httpReq.RequestUri!);
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
query[apiKey] = apiValue;
uriBuilder.Query = query.ToString();
httpReq.RequestUri = uriBuilder.Uri;
}
}
break;
case "Bearer Token":
case "JWT Bearer":
case "OAuth 2.0": // OAuth 2.0 uses Bearer tokens for authenticated requests
if (action.EndpointAuthToken is string authToken)
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
break;
case "Basic Auth":
if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword)
{
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}"));
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
}
break;
case "NTLM Auth":
if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername))
{
var credentials = new NetworkCredential(
action.EndpointAuthUsername,
action.EndpointAuthPassword,
action.EndpointAuthDomain);
var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } };
httpReq.Options.Set(new HttpRequestOptionsKey<CredentialCache>("Credentials"), credentialCache);
}
break;
case "Digest Auth":
case "OAuth 1.0":
case "AWS Signature":
// These authentication methods require more complex implementations,
// often involving multi-step handshakes or specialized libraries.
// They are left as placeholders for future implementation.
default:
throw new NotImplementedException(
$"The authentication type '{action.EndpointAuthType}' is not supported yet. " +
$"ProfileId: {action.ProfileId}, " +
$"Id: {action.Id}"
);
}
using var response = await http.SendAsync(httpReq, cancel);
var resBody = await response.Content.ReadAsStringAsync(cancel);
var resHeaders = response.Headers.ToDictionary();

View File

@@ -38,9 +38,9 @@ public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper
if (request.Invoked is bool invoked)
query = invoked ? query.Where(act => act.Root!.OutRes != null) : query.Where(act => act.Root!.OutRes == null);
var actions = await query.ToListAsync(cancel);
var actions = await query.Include(act => act.EndpointAuth).ToListAsync(cancel);
if(actions.Count == 0)
if (actions.Count == 0)
throw new NotFoundException($"No actions found for the profile {request.ProfileId}.");
return mapper.Map<IEnumerable<RecActionDto>>(actions);

View File

@@ -0,0 +1,7 @@
namespace ReC.Domain.Constants;
public enum ErrorAction
{
Stop = 0,
Continue = 1,
}

View File

@@ -10,8 +10,6 @@ public class OutRes
public short? Status { get; set; }
public string? Message { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }

View File

@@ -1,4 +1,6 @@
namespace ReC.Domain.Entities;
using ReC.Domain.Constants;
namespace ReC.Domain.Entities;
public class RecAction
{
@@ -36,7 +38,7 @@ public class RecAction
public string? PostprocessingQuery { get; set; }
public string? ErrorAction { get; set; }
public ErrorAction? ErrorAction { get; set; }
public string? AddedWho { get; set; }

View File

@@ -33,6 +33,9 @@ public class RecActionView
public long? EndpointAuthId { get; set; }
[ForeignKey("EndpointAuthId")]
public EndpointAuth? EndpointAuth { get; set; }
public string? EndpointAuthType { get; set; }
public string? EndpointAuthApiKey { get; set; }

View File

@@ -132,7 +132,6 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
b.Property(e => e.ActionId).HasColumnName("ACTION_ID");
b.Property(e => e.Status).HasColumnName("STATUS");
b.Property(e => e.Message).HasColumnName("MESSAGE");
b.Property(e => e.Header).HasColumnName("RESULT_HEADER");
b.Property(e => e.Body).HasColumnName("RESULT_BODY");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");