setup-api-authentication #1
Merged
barhen
merged 2 commits from setup-api-authentication
into dev
1 year ago
20 changed files with 444 additions and 675 deletions
@ -0,0 +1,32 @@ |
|||
using Cloud_Manager.Models.ClientContractModels; |
|||
using Cloud_Manager.Models.CustomModels; |
|||
using Cloud_Manager.Models.Interfaces; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace Cloud_Manager.Controllers |
|||
{ |
|||
[Route("api/[controller]")]
|
|||
[ApiController] |
|||
public class KeyGeneratorController : ControllerBase |
|||
{ |
|||
private readonly IKeyService m_keyService; |
|||
public KeyGeneratorController(IKeyService a_keyService) |
|||
{ |
|||
m_keyService = a_keyService; |
|||
} |
|||
|
|||
[HttpPost, Route("generate-key")] |
|||
public async Task<IActionResult> GenerateKeyAsync(Contract a_contract) |
|||
{ |
|||
if (await m_keyService.GenerateKey(a_contract)) |
|||
{ |
|||
return Ok("Key generated"); |
|||
} |
|||
else |
|||
{ |
|||
return BadRequest(); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,33 +0,0 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace Cloud_Manager.Controllers |
|||
{ |
|||
[ApiController] |
|||
[Route("[controller]")]
|
|||
public class WeatherForecastController : ControllerBase |
|||
{ |
|||
private static readonly string[] Summaries = new[] |
|||
{ |
|||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" |
|||
}; |
|||
|
|||
private readonly ILogger<WeatherForecastController> _logger; |
|||
|
|||
public WeatherForecastController(ILogger<WeatherForecastController> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
[HttpGet(Name = "GetWeatherForecast")] |
|||
public IEnumerable<WeatherForecast> Get() |
|||
{ |
|||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast |
|||
{ |
|||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), |
|||
TemperatureC = Random.Shared.Next(-20, 55), |
|||
Summary = Summaries[Random.Shared.Next(Summaries.Length)] |
|||
}) |
|||
.ToArray(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
using Cloud_Manager.Models.Enums; |
|||
using Cloud_Manager.Models.Interfaces; |
|||
using Cloud_Manager.Models.ServiceRepo; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Cloud_Manager.Middleware |
|||
{ |
|||
public class KeyValidationMiddleware |
|||
{ |
|||
private readonly RequestDelegate m_next; |
|||
|
|||
public KeyValidationMiddleware(RequestDelegate next) |
|||
{ |
|||
m_next = next; |
|||
} |
|||
|
|||
public async Task InvokeAsync(HttpContext a_httpContext, IKeyService a_keyService) |
|||
{ |
|||
string apiKey = a_httpContext.Request.Headers["BISK-API-KEY"]!; |
|||
AuthEnums status = a_keyService.ValidateKey(apiKey); |
|||
if (AuthEnums.Valid != status && a_httpContext.Request.Path != "/api/KeyGenerator/generate-key") |
|||
{ |
|||
a_httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; |
|||
await a_httpContext.Response.WriteAsync("API Key status : " + status); |
|||
|
|||
return; |
|||
} |
|||
|
|||
await m_next.Invoke(a_httpContext); |
|||
} |
|||
} |
|||
|
|||
public static class KeyValidationMiddlewareExtensions |
|||
{ |
|||
public static IApplicationBuilder UseKeyValidation( |
|||
this IApplicationBuilder builder) |
|||
{ |
|||
return builder.UseMiddleware<KeyValidationMiddleware>(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Cloud_Manager.Models.ClientContractModels; |
|||
|
|||
public partial class Clientapikey |
|||
{ |
|||
public int Id { get; set; } |
|||
|
|||
public string Key { get; set; } = null!; |
|||
|
|||
public int ContractId { get; set; } |
|||
|
|||
public ulong IsActive { get; set; } |
|||
} |
@ -0,0 +1,30 @@ |
|||
using Cloud_Manager.Models.ClientContractModels; |
|||
using Cloud_Manager.Models.Enums; |
|||
|
|||
namespace Cloud_Manager.Models.Interfaces |
|||
{ |
|||
public interface IKeyService |
|||
{ |
|||
/// <summary>
|
|||
/// Validates specified API
|
|||
/// </summary>
|
|||
/// <returns>AuthEnums.Valid if key is a valid and unexpired token</returns>
|
|||
AuthEnums ValidateKey(string a_Key); |
|||
|
|||
/// <summary>
|
|||
/// Generates an API Key based on the specified client
|
|||
/// </summary>
|
|||
Task<bool> GenerateKey(Contract a_clientContract); |
|||
/// <summary>
|
|||
///Returns the database Id if the API Key is valid to return the related database id
|
|||
/// </summary>
|
|||
/// <param name="a_Key"></param>
|
|||
int? GetDatabaseIdFromKey(string a_Key); |
|||
/// <summary>
|
|||
/// Gets the branch associated with the specified API if valid
|
|||
/// </summary>
|
|||
/// <param name="a_Key"></param>
|
|||
/// <returns></returns>
|
|||
string GetBaseBranch(string a_Key); |
|||
} |
|||
} |
@ -1,18 +0,0 @@ |
|||
using Cloud_Manager.Models.ClientContractModels; |
|||
using Cloud_Manager.Models.Enums; |
|||
|
|||
namespace Cloud_Manager.Models.Interfaces |
|||
{ |
|||
public interface ITokenService |
|||
{ |
|||
AuthEnums ValidateToken(string a_token); |
|||
string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database, List<string> a_business, bool a_comparison); |
|||
int? GetDatabaseIdFromToken(string a_token); |
|||
int? GetUserIdFromToken(string a_token); |
|||
string? GetUserNameFromToken(string a_token); |
|||
string? GetBaseBranch(string a_token); |
|||
bool? GetComparison(string a_token); |
|||
IEnumerable<string> BranchIds(string a_token); |
|||
string? GetAllBranch(string a_token); |
|||
} |
|||
} |
@ -1,199 +1,165 @@ |
|||
using Cloud_Manager.Models.ClientContractModels; |
|||
using Cloud_Manager.Models.Enums; |
|||
using Cloud_Manager.Models.Interfaces; |
|||
using Microsoft.EntityFrameworkCore.Metadata.Internal; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using System; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
|
|||
namespace Cloud_Manager.Models.ServiceRepo |
|||
{ |
|||
public class TokenService : ITokenService |
|||
public class TokenService : IKeyService |
|||
{ |
|||
private IConfiguration m_configuration { get; } |
|||
public TokenService(IConfiguration a_configuration) |
|||
private readonly Random m_random; |
|||
private BiskilogContext m_context; |
|||
public TokenService(IConfiguration a_configuration, BiskilogContext a_context) |
|||
{ |
|||
m_configuration = a_configuration; |
|||
m_context = a_context; |
|||
m_random = new Random(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates a user access token
|
|||
/// </summary>
|
|||
/// <returns>AuthEnums.Valid if token is a valid and unexpired token</returns>
|
|||
public AuthEnums ValidateToken(string a_token) |
|||
public AuthEnums ValidateKey(string a_Key) |
|||
{ |
|||
try |
|||
if (!string.IsNullOrEmpty(a_Key)) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
|
|||
if (jwtToken.ValidFrom <= DateTime.Now && jwtToken.ValidTo > DateTime.Now) |
|||
return AuthEnums.Valid; |
|||
return AuthEnums.Expired; |
|||
} |
|||
catch (Exception ex) |
|||
Clientapikey? keyInfo = m_context.Clientapikeys.FirstOrDefault(k => k.Key == a_Key); |
|||
if (keyInfo != null) |
|||
{ |
|||
return AuthEnums.Invalid; |
|||
} |
|||
if (keyInfo.IsActive == 0) |
|||
{ |
|||
//Key is not active
|
|||
return AuthEnums.Inactive; |
|||
} |
|||
/// <summary>
|
|||
/// Generates an access token based on the user
|
|||
/// </summary>
|
|||
/// <returns>A tokenized string</returns>
|
|||
public string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database, List<string> a_business, bool a_comparison) |
|||
|
|||
if (TryDecodeKey(a_Key, out int businessId)) |
|||
{ |
|||
try |
|||
Contract? contract = m_context.Contracts.FirstOrDefault(c => c.ContractId == keyInfo.ContractId && c.BusinessId == businessId && c.StartDate <= DateTime.Now && c.EndDate > DateTime.Now); |
|||
|
|||
if (contract == null) |
|||
{ |
|||
//create claims details based on the user information
|
|||
var claims = new[] { |
|||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), |
|||
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()), |
|||
new Claim("ContractStart",a_clientContract.StartDate !.Value.ToString()), |
|||
new Claim("ContractEnd",a_clientContract.EndDate!.Value.ToString()), |
|||
new Claim("UserId", a_user.UserId.ToString()), |
|||
new Claim("Username", a_user.Username.ToString()), |
|||
new Claim("DbId",a_database.DbNo.ToString()), |
|||
new Claim("ComparisonMode",a_comparison.ToString()), |
|||
new Claim("BranchId",a_business[0].ToString()), |
|||
new Claim("BranchAccess",string.Join(", ", a_business.ToArray())), |
|||
new Claim("ClientId", a_user.ClientId.ToString()), |
|||
}; |
|||
contract = m_context.Contracts.FirstOrDefault(c => c.ContractId == keyInfo.ContractId && c.BusinessId == businessId); |
|||
|
|||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(m_configuration["Jwt:Key"]!)); |
|||
//If contract start date is not past the key should inactive
|
|||
if (contract?.StartDate > DateTime.Now) |
|||
{ |
|||
return AuthEnums.Inactive; |
|||
} |
|||
|
|||
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); |
|||
//Anyother reason contract is expired
|
|||
return AuthEnums.Expired; |
|||
} |
|||
|
|||
var token = new JwtSecurityToken(m_configuration["Jwt:Issuer"], m_configuration["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(14), signingCredentials: signIn); |
|||
return $"{new JwtSecurityTokenHandler().WriteToken(token)}"; |
|||
//Key is valid and contract not expired
|
|||
return AuthEnums.Valid; |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
else |
|||
{ |
|||
Console.WriteLine(ex.Message); |
|||
return AuthEnums.Error.ToString(); |
|||
return AuthEnums.NotFound; |
|||
} |
|||
} |
|||
/// <summary>
|
|||
///Deserializes the token string if valid to return the specified user role id in the token string
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns>RoleId</returns>
|
|||
public int? GetDatabaseIdFromToken(string a_token) |
|||
|
|||
return AuthEnums.Invalid; |
|||
} |
|||
|
|||
public async Task<bool> GenerateKey(Contract a_clientContract) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
const string prefix = "AI"; |
|||
const char delimiter = '@'; |
|||
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
|||
|
|||
StringBuilder key = new StringBuilder(32); |
|||
key.Append(prefix); |
|||
key.Append(a_clientContract.BusinessId); |
|||
key.Append(delimiter); |
|||
|
|||
for (int i = key.Length; i < 32; i++) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return int.Parse(jwtToken.Claims.First(claim => claim.Type == "DbId").Value); |
|||
} |
|||
return null; |
|||
key.Append(chars[m_random.Next(chars.Length)]); |
|||
} |
|||
/// <summary>
|
|||
///Deserializes the token string if valid to return the specified user id in the token string
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns>UserId</returns>
|
|||
public int? GetUserIdFromToken(string a_token) |
|||
|
|||
Clientapikey clientapikey = new Clientapikey(); |
|||
clientapikey.Key = key.ToString(); |
|||
clientapikey.ContractId = a_clientContract.ContractId; |
|||
|
|||
m_context.Clientapikeys.Add(clientapikey); |
|||
|
|||
if (await m_context.SaveChangesAsync() > 0) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return int.Parse(jwtToken.Claims.First(claim => claim.Type == "UserId").Value); |
|||
return false; |
|||
} |
|||
return null; |
|||
} |
|||
/// <summary>
|
|||
///Deserializes the token string if valid to return the specified username in the token string
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns>Username</returns>
|
|||
public string? GetUserNameFromToken(string a_token) |
|||
|
|||
public int? GetDatabaseIdFromKey(string a_Key) |
|||
{ |
|||
if (ValidateKey(a_Key) == AuthEnums.Valid) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
if (TryDecodeKey(a_Key, out int businessId)) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return jwtToken.Claims.First(claim => claim.Type == "Username").Value; |
|||
Clientapikey? keyInfo = m_context.Clientapikeys.FirstOrDefault(k => k.Key == a_Key); |
|||
Contract? contract = m_context.Contracts.FirstOrDefault(c => c.ContractId == keyInfo.ContractId && c.BusinessId == businessId && c.StartDate <= DateTime.Now && c.EndDate > DateTime.Now); |
|||
|
|||
Databasemap? databaseMap = m_context.Databasemaps.FirstOrDefault(c => c.ClientId == contract.ClientId); |
|||
|
|||
return databaseMap?.DbNo; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
/// <summary>
|
|||
///Deserializes the token string if valid to return the specified branchId in the token string
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns>Username</returns>
|
|||
public string? GetBaseBranch(string a_token) |
|||
|
|||
public string GetBaseBranch(string a_Key) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
if (ValidateKey(a_Key) == AuthEnums.Valid) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return jwtToken.Claims.First(claim => claim.Type == "BranchId").Value; |
|||
} |
|||
return null; |
|||
} |
|||
if (TryDecodeKey(a_Key, out int businessId)) |
|||
{ |
|||
Clientapikey? keyInfo = m_context.Clientapikeys.FirstOrDefault(k => k.Key == a_Key); |
|||
Contract? contract = m_context.Contracts.FirstOrDefault(c => c.ContractId == keyInfo.ContractId && c.BusinessId == businessId && c.StartDate <= DateTime.Now && c.EndDate > DateTime.Now); |
|||
|
|||
public bool? GetComparison(string a_token) |
|||
if (contract != null) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
Clientbusiness? clientbusiness = m_context.Clientbusinesses.FirstOrDefault(cb => cb.ClientId == contract.ClientId && cb.BusinessId == businessId); |
|||
if (clientbusiness != null) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return bool.Parse(jwtToken.Claims.First(claim => claim.Type == "ComparisonMode").Value); |
|||
return clientbusiness.BusinessExternalId; |
|||
} |
|||
return null; |
|||
} |
|||
/// <summary>
|
|||
///Deserializes the token string if valid to return the specified list of branches a user has access to in the token string
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns>Username</returns>
|
|||
public string? GetAllBranch(string a_token) |
|||
{ |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
{ |
|||
string token = a_token.Substring(6).Trim(); |
|||
var handler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token); |
|||
return jwtToken.Claims.First(claim => claim.Type == "BranchAccess").Value; |
|||
} |
|||
return null; |
|||
} |
|||
/// <summary>
|
|||
/// Return a specified list of branches a user has access if comparison mode is set otherwise returns only the
|
|||
/// active branch on the list
|
|||
/// </summary>
|
|||
/// <param name="a_token"></param>
|
|||
/// <returns></returns>
|
|||
public IEnumerable<string> BranchIds(string a_token) |
|||
return String.Empty; |
|||
} |
|||
public static bool TryDecodeKey(string a_key, out int o_businessId) |
|||
{ |
|||
List<string> branchIds = new List<string>(); |
|||
if (ValidateToken(a_token) == AuthEnums.Valid) |
|||
char delimiter = '@'; |
|||
o_businessId = 0; |
|||
|
|||
// Check if the key has the expected length and starts with the expected prefix
|
|||
if (a_key.Length == 32 && a_key.StartsWith("AI")) |
|||
{ |
|||
bool comparison = GetComparison(a_token)!.Value; |
|||
if (comparison) |
|||
// Find the index of the delimiter
|
|||
int delimiterIndex = a_key.IndexOf(delimiter, 2); |
|||
|
|||
// Check if the delimiter is found and there are characters after it
|
|||
if (delimiterIndex != -1 && delimiterIndex < a_key.Length - 1) |
|||
{ |
|||
string? branches = GetAllBranch(a_token); |
|||
if (branches != null) |
|||
// Attempt to parse the embedded integer value
|
|||
if (int.TryParse(a_key.Substring(2, delimiterIndex - 2), out o_businessId)) |
|||
{ |
|||
string[] branchArray = branches!.Split(); |
|||
branchIds.AddRange(branchArray); |
|||
} |
|||
return true; // Successfully decoded
|
|||
} |
|||
else |
|||
{ |
|||
string? baseBranch = GetBaseBranch(a_token); |
|||
branchIds.Add(baseBranch!); |
|||
} |
|||
} |
|||
return branchIds.AsEnumerable(); |
|||
|
|||
return false; // Failed to decode
|
|||
} |
|||
} |
|||
} |
|||
|
Loading…
Reference in new issue