refactor(ConsumerService): Entfernt ReadLocalAsync Methode.

- LocalConsumer Eigenschaft in AuthApiParams hinzugefügt.
This commit is contained in:
Developer 02 2025-01-23 10:31:27 +01:00
parent f77a68be8d
commit efae188d5c
10 changed files with 45 additions and 146 deletions

View File

@ -4,46 +4,18 @@ namespace DigitalData.Auth.API.Config
{ {
public class AuthApiParams public class AuthApiParams
{ {
private IEnumerable<Consumer> _consumers = new List<Consumer>();
public IEnumerable<Consumer> Consumers
{
get => _consumers;
init
{
_consumers = value;
for (int i = 0; i < _consumers.Count(); i++)
_consumers.ElementAt(i).Parent = this;
}
}
public Consumer DefaultConsumer => Consumers.First();
public CookieOptionsProvider DefaultCookieOptions { get; init; } = new() public CookieOptionsProvider DefaultCookieOptions { get; init; } = new()
{ {
HttpOnly = true, HttpOnly = true,
SameSite = SameSiteMode.Strict SameSite = SameSiteMode.Strict
}; };
public string CookieName { get; init; } = "AuthToken"; public string DefaultCookieName { get; init; } = "AuthToken";
public required string Issuer { get; init; } public required string Issuer { get; init; }
public bool RequireHttpsMetadata { get; init; } = true; public bool RequireHttpsMetadata { get; init; } = true;
public class Consumer public required Consumer LocalConsumer { get; init; }
{
public required string Route { get; init; }
public required string Audience { get; init; }
private CookieOptionsProvider? _cookieOptions;
#pragma warning disable CS8603 // Possible null reference return.
public CookieOptionsProvider CookieOptions { get => _cookieOptions ?? Parent?.DefaultCookieOptions; init => _cookieOptions = value; }
#pragma warning restore CS8603 // Possible null reference return.
internal AuthApiParams? Parent { private get; set; }
}
} }
} }

View File

@ -1,27 +0,0 @@
using static DigitalData.Auth.API.Config.AuthApiParams;
namespace DigitalData.Auth.API.Config
{
public static class ConfigExtensions
{
public static Consumer? GetByAudience(this IEnumerable<Consumer> audiances, string name) => audiances.FirstOrDefault(a => a.Audience == name);
public static Consumer? GetByRoute(this IEnumerable<Consumer> audiances, string route) => audiances.FirstOrDefault(a => a.Route == route);
public static bool TryGetByAudience(this IEnumerable<Consumer> audiances, string audience, out Consumer audiance)
{
#pragma warning disable CS8601 // Possible null reference assignment.
audiance = audiances.GetByAudience(audience);
#pragma warning restore CS8601 // Possible null reference assignment.
return audiance is not null;
}
public static bool TryGetByRoute(this IEnumerable<Consumer> audiances, string route, out Consumer audiance)
{
#pragma warning disable CS8601 // Possible null reference assignment.
audiance = audiances.SingleOrDefault(a => a.Route == route);
#pragma warning restore CS8601 // Possible null reference assignment.
return audiance is not null;
}
}
}

View File

@ -1,39 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Options;
namespace DigitalData.Auth.API.Config
{
public static class DependentExtensions
{
private static AuthApiParams? _authApiParams;
private static AuthApiParams AuthApiParams
{
get => _authApiParams
?? throw new InvalidOperationException(
$"DependentExtensions have not been added to the application or are not configured correctly. {typeof(AuthApiParams)} cannot be provided."
);
set => _authApiParams = value;
}
public static IApplicationBuilder AddDependentExtensions(this IApplicationBuilder application)
{
var authApiParamOptions = application.ApplicationServices.GetRequiredService<IOptions<AuthApiParams>>();
_authApiParams = authApiParamOptions.Value;
return application;
}
public static bool TryGetByRoute(this IEnumerable<IAsymmetricTokenDescriptor> descriptors, string consumerRoute, out IAsymmetricTokenDescriptor descriptor)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
descriptor = null;
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
if (!AuthApiParams.Consumers.TryGetByRoute(consumerRoute, out var consumer)
|| !descriptors.TryGet(AuthApiParams.Issuer, consumer.Audience, out var _descriptor))
return false;
descriptor = _descriptor;
return true;
}
}
}

View File

@ -45,7 +45,7 @@ namespace DigitalData.Auth.API.Controllers
_consumerSignatureHandler = apiSignatureHandler; _consumerSignatureHandler = apiSignatureHandler;
} }
private async Task<IActionResult> CreateTokenAsync(LogInDto login, string consumerRoute, bool cookie = true) private async Task<IActionResult> CreateTokenAsync(LogInDto login, string consumerName, bool cookie = true)
{ {
bool isValid = await _dirSearchService.ValidateCredentialsAsync(login.Username, login.Password); bool isValid = await _dirSearchService.ValidateCredentialsAsync(login.Username, login.Password);
@ -53,8 +53,12 @@ namespace DigitalData.Auth.API.Controllers
return Unauthorized(); return Unauthorized();
//find the user //find the user
var uRes = await _userService.ReadByUsernameAsync(login.Username); var uRes = await _userService.ReadByUsernameAsync(login.Username);
if (uRes.IsFailed || !_apiParams.Consumers.TryGetByRoute(consumerRoute, out var consumer)) if (uRes.IsFailed)
return Unauthorized();
var consumer = await _consumerService.ReadByNameAsync(consumerName);
if (consumer is null)
return Unauthorized(); return Unauthorized();
if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, consumer.Audience, out var descriptor)) if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, consumer.Audience, out var descriptor))
@ -65,7 +69,8 @@ namespace DigitalData.Auth.API.Controllers
//set cookie //set cookie
if (cookie) if (cookie)
{ {
Response.Cookies.Append(_apiParams.CookieName, token, consumer.CookieOptions.Create(lifetime: descriptor.Lifetime)); var cookieOptions = consumer.CookieOptions ?? _apiParams.DefaultCookieOptions;
Response.Cookies.Append(_apiParams.DefaultCookieName, token, cookieOptions.Create(lifetime: descriptor.Lifetime));
return Ok(); return Ok();
} }
else else
@ -74,20 +79,20 @@ namespace DigitalData.Auth.API.Controllers
private async Task<IActionResult> CreateTokenAsync(ConsumerLogin login, bool cookie = true) private async Task<IActionResult> CreateTokenAsync(ConsumerLogin login, bool cookie = true)
{ {
var api = await _consumerService.ReadByNameAsync(login.Name); var consumer = await _consumerService.ReadByNameAsync(login.Name);
if (consumer is null || consumer.Password != login.Password)
if (api is null || api.Password != login.Password)
return Unauthorized(); return Unauthorized();
if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, _apiParams.DefaultConsumer.Audience, out var descriptor)) if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, _apiParams.LocalConsumer.Audience, out var descriptor))
return StatusCode(StatusCodes.Status500InternalServerError); return StatusCode(StatusCodes.Status500InternalServerError);
var token = _consumerSignatureHandler.WriteToken(api, descriptor); var token = _consumerSignatureHandler.WriteToken(consumer, descriptor);
//set cookie //set cookie
if (cookie) if (cookie)
{ {
Response.Cookies.Append(_apiParams.CookieName, token, _apiParams.DefaultConsumer.CookieOptions.Create(lifetime: descriptor.Lifetime)); var cookieOptions = _apiParams.LocalConsumer.CookieOptions ?? _apiParams.DefaultCookieOptions;
Response.Cookies.Append(_apiParams.DefaultCookieName, token, cookieOptions.Create(lifetime: descriptor.Lifetime));
return Ok(); return Ok();
} }
else else
@ -95,13 +100,13 @@ namespace DigitalData.Auth.API.Controllers
} }
//TODO: Add role depends on group name //TODO: Add role depends on group name
[HttpPost("{consumerRoute}/login")] [HttpPost("{consumerName}/login")]
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> Login([FromForm] LogInDto login, [FromRoute] string consumerRoute) public async Task<IActionResult> Login([FromForm] LogInDto login, [FromRoute] string consumerName)
{ {
try try
{ {
return await CreateTokenAsync(login, consumerRoute, true); return await CreateTokenAsync(login, consumerName, true);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -130,7 +135,7 @@ namespace DigitalData.Auth.API.Controllers
{ {
try try
{ {
Response.Cookies.Delete(_apiParams.CookieName); Response.Cookies.Delete(_apiParams.DefaultCookieName);
return Ok(); return Ok();
} }
catch (Exception ex) catch (Exception ex)
@ -140,12 +145,12 @@ namespace DigitalData.Auth.API.Controllers
} }
} }
[HttpPost("{consumerRoute}")] [HttpPost("{consumerName}")]
public async Task<IActionResult> CreateTokenViaBody([FromBody] LogInDto login, [FromRoute] string consumerRoute, [FromQuery] bool cookie = false) public async Task<IActionResult> CreateTokenViaBody([FromBody] LogInDto login, [FromRoute] string consumerName, [FromQuery] bool cookie = false)
{ {
try try
{ {
return await CreateTokenAsync(login, consumerRoute, cookie); return await CreateTokenAsync(login, consumerName, cookie);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -68,6 +68,6 @@
public bool IsEssential { get => _optionsBase.IsEssential; set => _optionsBase.IsEssential = value; } public bool IsEssential { get => _optionsBase.IsEssential; set => _optionsBase.IsEssential = value; }
#endregion #endregion
public CookieOptions Create() => new(_optionsBase) { Expires = DateTime.UtcNow.AddTicks(Lifetime.Ticks) }; public CookieOptions Create(TimeSpan? lifetime = null) => new(_optionsBase) { Expires = DateTime.UtcNow.AddTicks(lifetime?.Ticks ?? Lifetime.Ticks) };
} }
} }

View File

@ -85,13 +85,13 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
{ {
options.RequireHttpsMetadata = apiParams!.RequireHttpsMetadata; options.RequireHttpsMetadata = apiParams!.RequireHttpsMetadata;
options.ClaimsIssuer = apiParams!.Issuer; options.ClaimsIssuer = apiParams!.Issuer;
options.Audience = apiParams!.DefaultConsumer.Audience; options.Audience = apiParams.LocalConsumer.Audience;
options.TokenValidationParameters = new() options.TokenValidationParameters = new()
{ {
ValidateIssuer = true, ValidateIssuer = true,
ValidIssuer = apiParams!.Issuer, ValidIssuer = apiParams!.Issuer,
ValidateAudience = true, ValidateAudience = true,
ValidAudience = apiParams!.DefaultConsumer.Audience, ValidAudience = apiParams.LocalConsumer.Audience,
ValidateLifetime = true, ValidateLifetime = true,
IssuerSigningKey = issuerSigningKeyInitiator?.Value IssuerSigningKey = issuerSigningKeyInitiator?.Value
}; };
@ -102,7 +102,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
{ {
// if there is no token read related cookie // if there is no token read related cookie
if (context.Token is null // if there is no token if (context.Token is null // if there is no token
&& context.Request.Cookies.TryGetValue(apiParams!.CookieName, out var token) // get token from cookies && context.Request.Cookies.TryGetValue(apiParams!.DefaultCookieName, out var token) // get token from cookies
&& token is not null) && token is not null)
context.Token = token; context.Token = token;
return Task.CompletedTask; return Task.CompletedTask;
@ -112,12 +112,10 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
var app = builder.Build(); var app = builder.Build();
app.AddDependentExtensions();
issuerSigningKeyInitiator = new Lazy<SecurityKey>(() => issuerSigningKeyInitiator = new Lazy<SecurityKey>(() =>
{ {
var factory = app.Services.GetRequiredService<ICryptoFactory>(); var factory = app.Services.GetRequiredService<ICryptoFactory>();
var desc = factory.TokenDescriptors.Get(apiParams.Issuer, apiParams.DefaultConsumer.Audience); var desc = factory.TokenDescriptors.Get(apiParams.Issuer, apiParams.LocalConsumer.Audience);
return desc.Validator.SecurityKey; return desc.Validator.SecurityKey;
}); });

View File

@ -9,20 +9,15 @@ namespace DigitalData.Auth.API.Services
{ {
private readonly IEnumerable<Consumer> _consumers; private readonly IEnumerable<Consumer> _consumers;
private readonly AuthApiParams _authApiParams; public ConfiguredConsumerService(IOptions<IEnumerable<Consumer>> consumeroptions)
public ConfiguredConsumerService(IOptions<IEnumerable<Consumer>> consumeroptions, IOptions<AuthApiParams> authApiParamOptions)
{ {
_consumers = consumeroptions.Value; _consumers = consumeroptions.Value;
_authApiParams = authApiParamOptions.Value;
} }
public Task<Consumer?> ReadByIdAsync(int id) => Task.Run(() => _consumers.FirstOrDefault(api => api.Id == id)); public Task<Consumer?> ReadByIdAsync(int id) => Task.Run(() => _consumers.FirstOrDefault(api => api.Id == id));
public Task<Consumer?> ReadByNameAsync(string name) => Task.Run(() => _consumers.FirstOrDefault(api => api.Name == name)); public Task<Consumer?> ReadByNameAsync(string name) => Task.Run(() => _consumers.FirstOrDefault(api => api.Name == name));
public Task<Consumer> ReadLocalAsync() => Task.Run(() => _consumers.FirstOrDefault() ?? throw new InvalidOperationException("Unable to read the local consumer because no consumers are available."));
public async Task<bool> VerifyAsync(string name, string password) => (await ReadByNameAsync(name))?.Password == password; public async Task<bool> VerifyAsync(string name, string password) => (await ReadByNameAsync(name))?.Password == password;
} }
} }

View File

@ -8,8 +8,6 @@ namespace DigitalData.Auth.API.Services.Contracts
public Task<Consumer?> ReadByNameAsync(string name); public Task<Consumer?> ReadByNameAsync(string name);
public Task<Consumer> ReadLocalAsync();
public Task<bool> VerifyAsync(string name, string password); public Task<bool> VerifyAsync(string name, string password);
} }
} }

View File

@ -14,17 +14,13 @@
"ServerName": "DD-VMP01-DC01", "ServerName": "DD-VMP01-DC01",
"Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works" "Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works"
}, },
"Consumers": [
{
"Route": "api",
"Audience": "api.digitaldata.works"
},
{
"Route": "work-flow",
"Audience": "work-flow.digitaldata.works"
}
],
"Issuer": "auth.digitaldata.works", "Issuer": "auth.digitaldata.works",
"LocalConsumer": {
"Id": -1,
"Name": "auth-flow",
"Audience": "auth.digitaldata.works",
"Password": "n7l^)s,v;jbr0c+x%urk=fak4[s==z?<"
},
"CryptParams": { "CryptParams": {
"KeySizeInBits": 4096, "KeySizeInBits": 4096,
"Padding": "OaepSHA512", "Padding": "OaepSHA512",
@ -38,7 +34,7 @@
{ {
"Id": "4062504f-f081-43d1-b4ed-78256a0879e1", "Id": "4062504f-f081-43d1-b4ed-78256a0879e1",
"Issuer": "auth.digitaldata.works", "Issuer": "auth.digitaldata.works",
"Audience": "api.digitaldata.works", "Audience": "auth.digitaldata.works",
"IsEncrypted": true, "IsEncrypted": true,
"Lifetime": "5:00:00" "Lifetime": "5:00:00"
}, },
@ -48,6 +44,13 @@
"Audience": "work-flow.digitaldata.works", "Audience": "work-flow.digitaldata.works",
"IsEncrypted": true, "IsEncrypted": true,
"Lifetime": "02:00:00" "Lifetime": "02:00:00"
},
{
"Id": "9e3b0e68-c3e4-489e-b68c-47df26d6b612",
"Issuer": "auth.digitaldata.works",
"Audience": "user-manager.digitaldata.works",
"IsEncrypted": true,
"Lifetime": "02:00:00"
} }
] ]
} }

View File

@ -2,18 +2,12 @@
"Consumers": [ "Consumers": [
{ {
"Id": 0, "Id": 0,
"Name": "auth-flow",
"Audience": "auth.digitaldata.works",
"Password": "aQ9z!2@TgY7b#fHcD3pLmV1$wX"
},
{
"Id": 1,
"Name": "work-flow", "Name": "work-flow",
"Audience": "work-flow.digitaldata.works", "Audience": "work-flow.digitaldata.works",
"Password": "t3B|aiJ'i-snLzNRj3B{9=&:lM5P@'iL" "Password": "t3B|aiJ'i-snLzNRj3B{9=&:lM5P@'iL"
}, },
{ {
"Id": 2, "Id": 1,
"Name": "user-manager", "Name": "user-manager",
"Audience": "user-manager.digitaldata.works", "Audience": "user-manager.digitaldata.works",
"Password": "a098Hvu1-y29ep{KPQO]#>8TK+fk{O`_d" "Password": "a098Hvu1-y29ep{KPQO]#>8TK+fk{O`_d"