Compare commits
2 Commits
cc91622bde
...
b20dad7208
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20dad7208 | ||
|
|
a4735e0f44 |
50
Authorization/PermissionHandler.cs
Normal file
50
Authorization/PermissionHandler.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Security.Claims;
|
||||
using AspNetCore.Authentication.ApiKey;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace NejCommon.Authorization;
|
||||
|
||||
public partial class PermissionHandler : AuthorizationHandler<PermissionRequirement>
|
||||
{
|
||||
protected override async Task HandleRequirementAsync(
|
||||
AuthorizationHandlerContext context,
|
||||
PermissionRequirement requirement)
|
||||
{
|
||||
if (context.Resource is not HttpContext http)
|
||||
return;
|
||||
|
||||
var companyId = http.GetRouteValue("companyId")?.ToString();
|
||||
if (companyId == null)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return;
|
||||
}
|
||||
|
||||
var authType = context.User.Identity?.AuthenticationType;
|
||||
if (authType == null)
|
||||
return;
|
||||
|
||||
bool allowed;
|
||||
|
||||
if (authType == ApiKeyDefaults.AuthenticationScheme)
|
||||
{
|
||||
var id = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (id == null)
|
||||
return;
|
||||
|
||||
allowed = await CheckApiKeyPermission(id, companyId, requirement.Permission);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sub = context.User.FindFirst("sub")?.Value;
|
||||
if (sub == null)
|
||||
return;
|
||||
|
||||
allowed = await CheckUserPermission(sub, companyId, requirement.Permission);
|
||||
}
|
||||
|
||||
if (allowed)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
13
Authorization/PermissionRequirement.cs
Normal file
13
Authorization/PermissionRequirement.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace NejCommon.Authorization;
|
||||
|
||||
public class PermissionRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public string? Permission { get; }
|
||||
|
||||
public PermissionRequirement(string? permission)
|
||||
{
|
||||
Permission = permission;
|
||||
}
|
||||
}
|
||||
60
Models/ApiKey.cs
Normal file
60
Models/ApiKey.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using AspNetCore.Authentication.ApiKey;
|
||||
using AutoMapPropertyHelper;
|
||||
using NejCommon.Models;
|
||||
|
||||
namespace NejCommon.Models;
|
||||
|
||||
public partial class ApiKey : IApiKey
|
||||
{
|
||||
[AutoMapProperty("Response")]
|
||||
public string Id { get; set; } = NanoidDotNet.Nanoid.Generate(size: 64);
|
||||
[AutoMapProperty("Request")]
|
||||
public string Name { get; set; } = null!;
|
||||
public byte[] LookupHash { get; set; } = null!;
|
||||
public string KeyHash { get; set; } = null!;
|
||||
|
||||
[AutoMapProperty("Response")]
|
||||
public bool Revoked { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string key = "";
|
||||
[NotMapped]
|
||||
[AutoMapProperty("CreateResponse")]
|
||||
public string Key
|
||||
{
|
||||
get => key;
|
||||
set
|
||||
{
|
||||
key = value;
|
||||
LookupHash = ComputeLookupHash(value);
|
||||
KeyHash = ComputeStrongHash(value);
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
public string OwnerName => Id;
|
||||
[NotMapped]
|
||||
public IReadOnlyCollection<Claim> Claims => [
|
||||
new Claim(ClaimTypes.NameIdentifier, Id),
|
||||
new Claim(ClaimTypes.Name, Name)
|
||||
];
|
||||
|
||||
public static byte[] ComputeLookupHash(string key)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
return sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
|
||||
}
|
||||
public static string ComputeStrongHash(string key)
|
||||
{
|
||||
return BCrypt.Net.BCrypt.EnhancedHashPassword(key);
|
||||
}
|
||||
public bool VerifyStrongHash(string key){
|
||||
return BCrypt.Net.BCrypt.EnhancedVerify(key, KeyHash);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ApiKeyResponse : ApiKeyRequest {}
|
||||
public partial class ApiKeyCreateResponse : ApiKeyResponse {}
|
||||
43
Services/ApiKeyProvider.cs
Normal file
43
Services/ApiKeyProvider.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using AspNetCore.Authentication.ApiKey;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NanoidDotNet;
|
||||
using NejCommon.Models;
|
||||
|
||||
namespace NejCommon.Services;
|
||||
|
||||
public partial class ApiKeyProvider : IApiKeyProvider, IScopedService
|
||||
{
|
||||
private readonly ILogger<ApiKeyProvider> _logger;
|
||||
|
||||
public async Task<IApiKey> ProvideAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lookup = ApiKey.ComputeLookupHash(key);
|
||||
|
||||
var apiKey = await GetApiKeySet().FirstOrDefaultAsync(x => x.LookupHash.SequenceEqual(lookup));
|
||||
if (apiKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!apiKey.VerifyStrongHash(key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
apiKey.Key = key;
|
||||
|
||||
// write your validation implementation here and return an instance of a valid ApiKey or retun null for an invalid key.
|
||||
// return await _apiKeyRepository.GetApiKeyAsync(key);
|
||||
return apiKey;
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
_logger.LogError(exception, exception.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GenerateKey() => "nej-" + Nanoid.Generate(size: 256);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user