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
{
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()
{
HttpOnly = true,
SameSite = SameSiteMode.Strict
};
public string CookieName { get; init; } = "AuthToken";
public string DefaultCookieName { get; init; } = "AuthToken";
public required string Issuer { get; init; }
public bool RequireHttpsMetadata { get; init; } = true;
public class Consumer
{
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; }
}
public required Consumer LocalConsumer { get; init; }
}
}

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;
}
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);
@ -54,7 +54,11 @@ namespace DigitalData.Auth.API.Controllers
//find the user
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();
if (!_cryptoFactory.TokenDescriptors.TryGet(_apiParams.Issuer, consumer.Audience, out var descriptor))
@ -65,7 +69,8 @@ namespace DigitalData.Auth.API.Controllers
//set 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();
}
else
@ -74,20 +79,20 @@ namespace DigitalData.Auth.API.Controllers
private async Task<IActionResult> CreateTokenAsync(ConsumerLogin login, bool cookie = true)
{
var api = await _consumerService.ReadByNameAsync(login.Name);
if (api is null || api.Password != login.Password)
var consumer = await _consumerService.ReadByNameAsync(login.Name);
if (consumer is null || consumer.Password != login.Password)
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);
var token = _consumerSignatureHandler.WriteToken(api, descriptor);
var token = _consumerSignatureHandler.WriteToken(consumer, descriptor);
//set 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();
}
else
@ -95,13 +100,13 @@ namespace DigitalData.Auth.API.Controllers
}
//TODO: Add role depends on group name
[HttpPost("{consumerRoute}/login")]
[HttpPost("{consumerName}/login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromForm] LogInDto login, [FromRoute] string consumerRoute)
public async Task<IActionResult> Login([FromForm] LogInDto login, [FromRoute] string consumerName)
{
try
{
return await CreateTokenAsync(login, consumerRoute, true);
return await CreateTokenAsync(login, consumerName, true);
}
catch (Exception ex)
{
@ -130,7 +135,7 @@ namespace DigitalData.Auth.API.Controllers
{
try
{
Response.Cookies.Delete(_apiParams.CookieName);
Response.Cookies.Delete(_apiParams.DefaultCookieName);
return Ok();
}
catch (Exception ex)
@ -140,12 +145,12 @@ namespace DigitalData.Auth.API.Controllers
}
}
[HttpPost("{consumerRoute}")]
public async Task<IActionResult> CreateTokenViaBody([FromBody] LogInDto login, [FromRoute] string consumerRoute, [FromQuery] bool cookie = false)
[HttpPost("{consumerName}")]
public async Task<IActionResult> CreateTokenViaBody([FromBody] LogInDto login, [FromRoute] string consumerName, [FromQuery] bool cookie = false)
{
try
{
return await CreateTokenAsync(login, consumerRoute, cookie);
return await CreateTokenAsync(login, consumerName, cookie);
}
catch (Exception ex)
{

View File

@ -68,6 +68,6 @@
public bool IsEssential { get => _optionsBase.IsEssential; set => _optionsBase.IsEssential = value; }
#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.ClaimsIssuer = apiParams!.Issuer;
options.Audience = apiParams!.DefaultConsumer.Audience;
options.Audience = apiParams.LocalConsumer.Audience;
options.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidIssuer = apiParams!.Issuer,
ValidateAudience = true,
ValidAudience = apiParams!.DefaultConsumer.Audience,
ValidAudience = apiParams.LocalConsumer.Audience,
ValidateLifetime = true,
IssuerSigningKey = issuerSigningKeyInitiator?.Value
};
@ -102,7 +102,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
{
// if there is no token read related cookie
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)
context.Token = token;
return Task.CompletedTask;
@ -112,12 +112,10 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
var app = builder.Build();
app.AddDependentExtensions();
issuerSigningKeyInitiator = new Lazy<SecurityKey>(() =>
{
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;
});

View File

@ -9,20 +9,15 @@ namespace DigitalData.Auth.API.Services
{
private readonly IEnumerable<Consumer> _consumers;
private readonly AuthApiParams _authApiParams;
public ConfiguredConsumerService(IOptions<IEnumerable<Consumer>> consumeroptions, IOptions<AuthApiParams> authApiParamOptions)
public ConfiguredConsumerService(IOptions<IEnumerable<Consumer>> consumeroptions)
{
_consumers = consumeroptions.Value;
_authApiParams = authApiParamOptions.Value;
}
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> 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;
}
}

View File

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

View File

@ -14,17 +14,13 @@
"ServerName": "DD-VMP01-DC01",
"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",
"LocalConsumer": {
"Id": -1,
"Name": "auth-flow",
"Audience": "auth.digitaldata.works",
"Password": "n7l^)s,v;jbr0c+x%urk=fak4[s==z?<"
},
"CryptParams": {
"KeySizeInBits": 4096,
"Padding": "OaepSHA512",
@ -38,7 +34,7 @@
{
"Id": "4062504f-f081-43d1-b4ed-78256a0879e1",
"Issuer": "auth.digitaldata.works",
"Audience": "api.digitaldata.works",
"Audience": "auth.digitaldata.works",
"IsEncrypted": true,
"Lifetime": "5:00:00"
},
@ -48,6 +44,13 @@
"Audience": "work-flow.digitaldata.works",
"IsEncrypted": true,
"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": [
{
"Id": 0,
"Name": "auth-flow",
"Audience": "auth.digitaldata.works",
"Password": "aQ9z!2@TgY7b#fHcD3pLmV1$wX"
},
{
"Id": 1,
"Name": "work-flow",
"Audience": "work-flow.digitaldata.works",
"Password": "t3B|aiJ'i-snLzNRj3B{9=&:lM5P@'iL"
},
{
"Id": 2,
"Id": 1,
"Name": "user-manager",
"Audience": "user-manager.digitaldata.works",
"Password": "a098Hvu1-y29ep{KPQO]#>8TK+fk{O`_d"