Compare commits

...
2 Commits
Author SHA1 Message Date
saherelm 5085fc54a4 last ... 2026-10-09 23:17:51 +03:30
saherelm 2e8f269ae6 last ... 2026-10-09 19:35:50 +03:30
11 changed files with 222 additions and 41 deletions
@@ -27,8 +27,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("ApiKeys/{id}")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/ApiKeys/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApiKeyDto>> GetApiKey(
[FromRoute] Guid id,
@@ -75,8 +75,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpDelete("ApiKeys/{id}")]
[Authorize(LocalApi.PolicyName)]
[HttpDelete("Applications/ApiKeys/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApiKeyDto>> RemoveApiKey(
[FromRoute] Guid id,
@@ -123,8 +123,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpPut("ApiKeys/{id}")]
[Authorize(LocalApi.PolicyName)]
[HttpPut("Applications/ApiKeys/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApiKeyDto>> UpdateApiKey(
[FromRoute] Guid id,
@@ -173,8 +173,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("ApiKeys")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/ApiKeys")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<IEnumerable<XApiKeyDto>>> GetAllApiKeys(
CancellationToken cancellationToken = default
@@ -213,8 +213,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("ApiKeys/Query")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/ApiKeys/Query")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XQueryResult<XApiKeyDto>>> QueryApiKeys(
[FromRoute] XQuery query,
@@ -27,8 +27,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpPost("ApiKeys/Usages")]
[Authorize(LocalApi.PolicyName)]
[HttpPost("Applications/ApiKeys/Usages")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApiKeyUsageDto>> AddApiKeyUsage(
[FromBody] XApiKeyUsageDto item,
@@ -74,8 +74,8 @@ namespace xIds.Controllers
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpGet("ApiKeys/Usages/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpGet("Applications/ApiKeys/Usages/{id}")]
public async Task<ActionResult<XApiKeyUsageDto>> GetApiKeyUsage(
[FromRoute] long id,
CancellationToken cancellationToken = default
@@ -122,8 +122,8 @@ namespace xIds.Controllers
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpDelete("ApiKeys/Usages/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpDelete("Applications/ApiKeys/Usages/{id}")]
public async Task<ActionResult<XApiKeyUsageDto>> RemoveApiKeyUsage(
[FromRoute] long id,
CancellationToken cancellationToken = default
@@ -170,8 +170,8 @@ namespace xIds.Controllers
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpPut("ApiKeys/Usages/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPut("Applications/ApiKeys/Usages/{id}")]
public async Task<ActionResult<XApiKeyUsageDto>> UpdateApiKeyUsage(
[FromRoute] long id,
[FromBody] XApiKeyUsageDto item,
@@ -219,8 +219,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("ApiKeys/Usages")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/ApiKeys/Usages")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<IEnumerable<XApiKeyUsageDto>>> GetAllApiKeyUsages(
CancellationToken cancellationToken = default
@@ -260,7 +260,7 @@ namespace xIds.Controllers
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/ApiKeys/Usages/Query")]
[HttpGet("ApiKeys/Usages/Query")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XQueryResult<XApiKeyUsageDto>>> QueryApiKeyUsages(
[FromRoute] XQuery query,
@@ -36,8 +36,8 @@ namespace xIds.Controllers
/// <param name="item"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost("")]
[RequireXPowered]
[HttpPost("Applications")]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApplicationDto>> CreateApplication(
@@ -93,8 +93,8 @@ namespace xIds.Controllers
/// <param name="item"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPut("")]
[RequireXPowered]
[HttpPut("Applications")]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApplicationDto>> UpdateApplication(
@@ -141,8 +141,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpDelete("{id}")]
[Authorize(LocalApi.PolicyName)]
[HttpDelete("Applications/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApplicationDto>> RemoveApplication(
[FromRoute] int id,
@@ -188,8 +188,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("{id}")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/{id}")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XApplicationDto>> GetApplication(
[FromRoute] int id,
@@ -234,9 +234,9 @@ namespace xIds.Controllers
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet("")]
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<IEnumerable<XApplicationDto>>> GetAllApplications(
CancellationToken cancellationToken = default
@@ -275,8 +275,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpGet("Query")]
[Authorize(LocalApi.PolicyName)]
[HttpGet("Applications/Query")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XQueryResult<XApplicationDto>>> QueryApplications(
[FromRoute] XQuery query,
@@ -18,6 +18,57 @@ namespace xIds.Controllers
{
public partial class ApplicationsController
{
/// <summary>
/// Show Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="applicationId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpGet("{applicationId}/ApiKeys/{apiKeyId}/Show")]
public async Task<ActionResult<string>> ShowApiKey(
[FromRoute] Guid apiKeyId,
[FromRoute] int applicationId,
CancellationToken cancellationToken = default
)
{
//
try
{
//
// Validate ...
if (
apiKeyId.IsDefaultGuid() ||
!applicationId.IsValidIntId())
{
XException.InvalidArgs.Throw();
}
//
var userInfo = await GetUserInfo();
//
var result = await provider.ShowApiKey(
userInfo: userInfo,
apiKeyId: apiKeyId,
applicationId: applicationId,
cancellationToken: cancellationToken
);
//
return Ok(result);
}
catch (Exception ex)
{
//
var result = GetExceptionActionResult(ex);
return result;
}
}
/// <summary>
/// Create Api Key ...
/// </summary>
@@ -28,7 +79,7 @@ namespace xIds.Controllers
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPost("Applications/{applicationId}/ApiKeys/Create")]
[HttpPost("{applicationId}/ApiKeys/Create")]
public async Task<ActionResult<XApiKeyDto>> CreateApiKey(
[FromRoute] int applicationId,
[FromBody] XCreateApiKeyRequest request,
@@ -63,6 +114,27 @@ namespace xIds.Controllers
XException.NotFound.Throw();
}
//
// Noramlize TimeSpan ...
var expirationTimeSpan = TimeSpan
.FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes);
if (
request.ExpirationMinutes.HasValue &&
request.ExpirationMinutes.Value > 0
)
{
//
// Normalize Expiration Minutes ...
int minutes = request.ExpirationMinutes.Value;
if (minutes > apiKeyConfiguration.MaxExpirationMinutes)
{
minutes = apiKeyConfiguration.MaxExpirationMinutes;
}
//
expirationTimeSpan = TimeSpan.FromMinutes(minutes);
}
//
var result = await provider.CreateApiKey(
userInfo: userInfo,
@@ -70,7 +142,7 @@ namespace xIds.Controllers
applicationId: applicationId,
rateLimit: request.RateLimit,
allowedIPs: request.AllowedIPs,
expiration: request.Expiration,
expiration: expirationTimeSpan,
cancellationToken: cancellationToken
);
@@ -95,7 +167,7 @@ namespace xIds.Controllers
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpDelete("Applications/{applicationId}/ApiKeys/{apiKeyId}")]
[HttpDelete("{applicationId}/ApiKeys/{apiKeyId}")]
public async Task<ActionResult<XApiKeyDto>> RemoveApiKey(
[FromRoute] Guid apiKeyId,
[FromRoute] int applicationId,
@@ -160,7 +232,7 @@ namespace xIds.Controllers
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPost("Applications/{applicationId}/ApiKeys/{apiKeyId}")]
[HttpPost("{applicationId}/ApiKeys/{apiKeyId}")]
public async Task<ActionResult<XApiKeyDto>> RotateApiKey(
[FromRoute] Guid apiKeyId,
[FromRoute] int applicationId,
@@ -241,7 +313,7 @@ namespace xIds.Controllers
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPost("Applications/{applicationId}/ApiKeys/{apiKeyId}/Revoke")]
[HttpPost("{applicationId}/ApiKeys/{apiKeyId}/Revoke")]
public async Task<ActionResult<bool>> RevokeApiKey(
[FromRoute] Guid apiKeyId,
[FromRoute] int applicationId,
@@ -302,8 +374,8 @@ namespace xIds.Controllers
/// <returns></returns>
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[HttpGet("{applicationId}/ApiKeys")]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPost("Applications/{applicationId}/ApiKeys")]
public async Task<ActionResult<IEnumerable<XApiKeyDto>>> GetApiKeys(
[FromRoute] int applicationId,
CancellationToken cancellationToken = default
@@ -363,7 +435,7 @@ namespace xIds.Controllers
[RequireXPowered]
[Authorize(LocalApi.PolicyName)]
[Authorize(Policy = XPolicies.EnabledUser)]
[HttpPost("Applications/{applicationId}/ApiKeys/{apiKeyId}/Usages")]
[HttpGet("{applicationId}/ApiKeys/{apiKeyId}/Usages")]
public async Task<ActionResult<IEnumerable<XApiKeyUsageDto>>> GetApiKeyUsages(
[FromRoute] Guid apiKeyId,
[FromRoute] int applicationId,
@@ -439,8 +511,8 @@ namespace xIds.Controllers
/// <param name="cancellationToken"></param>
/// <returns></returns>
[RequireXPowered]
[HttpPost("ApiKeys/Validate")]
[Authorize(LocalApi.PolicyName)]
[HttpPost("Applications/ApiKeys/Validate")]
public async Task<ActionResult<XApiKeyValidationResult>> ValidateApiKey(
[FromBody] XApiKeyValidationRequest request,
CancellationToken cancellationToken = default
+17
View File
@@ -176,11 +176,13 @@ namespace xIds.Interfaces
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="apiKey"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> AddApiKeyAsync(
XApiKeyDto item,
string apiKey = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
@@ -480,6 +482,21 @@ namespace xIds.Interfaces
//
#region Custom Actions ...
/// <summary>
/// Show Specifid Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="applicationId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> ShowApiKey(
Guid apiKeyId,
int applicationId,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Generate a New ApiKey and Add It to DB ...
/// </summary>
+25 -5
View File
@@ -14,6 +14,7 @@ using IdentityServer4.Extensions;
using xIdentityModels.Extensions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Caching.Memory;
using AutoMapper;
namespace xIds.Providers
{
@@ -28,6 +29,7 @@ namespace xIds.Providers
private readonly IXIdentityManager identityManager;
private readonly XDataServiceConfiguration configuration;
private readonly XApiKeyConfiguration apiKeyConfiguration;
private readonly Action<IMapperConfigurationExpression> mapperConfigurationExp;
public XApplicationProvider(
IMemoryCache cache,
@@ -45,6 +47,23 @@ namespace xIds.Providers
this.configuration = configuration;
this.identityManager = identityManager;
this.apiKeyConfiguration = apiKeyConfiguration;
//
// Configure Mapper Options ...
mapperConfigurationExp = cfg =>
{
//
cfg.CreateMap<XApiKey, XApiKeyDto>()
.ReverseMap();
//
cfg.CreateMap<XApplication, XApplicationDto>()
.ReverseMap();
//
cfg.CreateMap<XApiKeyUsage, XApiKeyUsageDto>()
.ReverseMap();
};
}
/// <summary>
@@ -288,14 +307,14 @@ namespace xIds.Providers
/// <param name="limitPerMinute"></param>
/// <returns></returns>
private bool CheckRateLimit(
Guid apiKeyId,
Guid apiKeyId,
int limitPerMinute
)
{
//
var cacheKey = $"ratelimit:{apiKeyId}";
var currentCount = cache.GetOrCreate(
cacheKey,
cacheKey,
entry =>
{
//
@@ -305,14 +324,15 @@ namespace xIds.Providers
);
//
if (currentCount >= limitPerMinute) {
if (currentCount >= limitPerMinute)
{
return false;
}
//
cache.Set(
cacheKey,
currentCount + 1,
cacheKey,
currentCount + 1,
TimeSpan.FromMinutes(1)
);
@@ -26,11 +26,13 @@ namespace xIds.Providers
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="apiKey"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> AddApiKeyAsync(
XApiKeyDto item,
string apiKey = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
@@ -85,7 +87,12 @@ namespace xIds.Providers
item.CreatedOn = DateTime.UtcNow;
//
var entity = item.MapConvert<XApiKey, XApiKeyDto>();
var entity = item.MapConvert<XApiKey, XApiKeyDto>(mapperConfigurationExp);
if (!apiKey.IsNullOrEmpty())
{
entity.Key = apiKey;
}
var entry = await dbContext.ApiKeys.AddAsync(
entity: entity,
cancellationToken: cancellationToken
@@ -17,6 +17,67 @@ namespace xIds.Providers
{
//
#region Custom Actions ...
/// <summary>
/// Show Specifid Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="applicationId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<string> ShowApiKey(
Guid apiKeyId,
int applicationId,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
apiKeyId.IsDefaultGuid() ||
userInfo.IsNullOrDefault() ||
!applicationId.IsValidIntId()
)
{
XException.InvalidArgs.Throw();
}
//
// Check Application Exists ...
var application = await GetApplicationAsync(
id: applicationId,
userInfo: userInfo,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Validate Owner ...
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
// Retrieve ApiKey Entity ...
// Since ApiKey is Only Exists on ApiKey Entity ...
var entity = await dbContext.ApiKeys
.FirstOrDefaultAsync(x => x.Id == apiKeyId);
if (entity.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
return entity.Key;
}
/// <summary>
/// Generate a New ApiKey and Add It to DB ...
/// </summary>
@@ -114,6 +175,7 @@ namespace xIds.Providers
// Add Item to Database ...
result = await AddApiKeyAsync(
item: result,
apiKey: plainKey,
userInfo: userInfo,
cancellationToken: cancellationToken
);
@@ -371,8 +433,8 @@ namespace xIds.Providers
.ParseListString<string>();
var allowedScopes = (keyModel.AllowedScopes ?? string.Empty)
.FromJSON<IEnumerable<string>>();
result.Scopes = [..allowedScopes];
result.Scopes = [.. allowedScopes];
//
// Check Application is Exists and Active ...
var application = await dbContext.Applications
@@ -25,7 +25,7 @@ namespace xIds.Providers
)
{
//
var result = source.MapConvert<XApiKeyDto, XApiKey>();
var result = source.MapConvert<XApiKeyDto, XApiKey>(mapperConfigurationExp);
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
@@ -88,7 +88,7 @@ namespace xIds.Providers
)
{
//
var result = source.MapConvert<XApiKeyUsageDto, XApiKeyUsage>();
var result = source.MapConvert<XApiKeyUsageDto, XApiKeyUsage>(mapperConfigurationExp);
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
@@ -110,7 +110,7 @@ namespace xIds.Providers
)
{
//
var result = source.MapConvert<XApplicationDto, XApplication>();
var result = source.MapConvert<XApplicationDto, XApplication>(mapperConfigurationExp);
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
+4 -1
View File
@@ -67,7 +67,10 @@ namespace xIds
//
// Register Authentication ...
services.AddXIdentityServerAuthentication(Configuration);
services.AddXIdentityServerAuthentication(
configuration: Configuration,
addApiKeyAuthentication: true
);
//
// Register Authorization ...
+4 -4
View File
@@ -37,12 +37,12 @@
"MaxFileSize": 41943040
},
"ApiKeyConfiguration": {
"ApiPrefix": "xapp",
"ApiKeyPrefix": "xapp",
"DefaultRateLimit": 60,
"EnableAuditLog": false,
"MaxKeysPerApplication": "10",
"MaxExpirationMinutes": "525600",
"DefaultExpirationMinutes": "43200"
"MaxKeysPerApplication": 10,
"MaxExpirationMinutes": 525600,
"DefaultExpirationMinutes": 43200
},
"DbSeeder": {
"UpdateExists": false,