From ce86d103039b22695b04714ee85e9ef3e1e032b5 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sun, 23 Jan 2022 11:41:42 +0100 Subject: feat(auth): Implements first draft of basic auth gen/validation --- src/server/Utilities/BasicAuthenticationHandler.cs | 62 ++++++++++++++++------ src/server/Utilities/SnakeCaseNamingPolicy.cs | 15 ++++++ 2 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 src/server/Utilities/SnakeCaseNamingPolicy.cs (limited to 'src/server/Utilities') diff --git a/src/server/Utilities/BasicAuthenticationHandler.cs b/src/server/Utilities/BasicAuthenticationHandler.cs index 7961b82..c4124e8 100644 --- a/src/server/Utilities/BasicAuthenticationHandler.cs +++ b/src/server/Utilities/BasicAuthenticationHandler.cs @@ -8,10 +8,21 @@ namespace IOL.BookmarkThing.Server.Utilities; public class BasicAuthenticationHandler : AuthenticationHandler { private readonly AppDbContext _context; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; - public BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AppDbContext context) : + public BasicAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + AppDbContext context, + IConfiguration configuration + ) : base(options, logger, encoder, clock) { _context = context; + _configuration = configuration; + _logger = logger.CreateLogger(); } protected override Task HandleAuthenticateAsync() { @@ -23,26 +34,45 @@ public class BasicAuthenticationHandler : AuthenticationHandler("TOKEN_ENTROPY"); + if (token_entropy.IsNullOrWhiteSpace()) { + _logger.LogWarning("No token entropy is available in env:TOKEN_ENTROPY, Basic auth is disabled"); + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); + } + var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); if (authHeader.Parameter == null) return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); var credentialBytes = Convert.FromBase64String(authHeader.Parameter); - var token_is_guid = Guid.TryParse(Encoding.UTF8.GetString(credentialBytes), out var token_id); - if (token_is_guid) { - var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == token_id); - if (token == default) { - return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); - } - - var claims = token.User.DefaultClaims(); - var identity = new ClaimsIdentity(claims, Scheme.Name); - var principal = new ClaimsPrincipal(identity); - var ticket = new AuthenticationTicket(principal, Scheme.Name); - - return Task.FromResult(AuthenticateResult.Success(ticket)); + var decrypted_string = Encoding.UTF8.GetString(credentialBytes).DecryptWithAes(token_entropy); + var token_is_guid = Guid.TryParse(decrypted_string, out var token_id); + + if (!token_is_guid) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); } - return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); - } catch { + var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == token_id); + if (token == default) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Not Found")); + } + + if (token.HasExpired) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Expired")); + } + + var permissions = new List() { + new(Constants.TOKEN_ALLOW_READ, token.AllowRead.ToString()), + new(Constants.TOKEN_ALLOW_UPDATE, token.AllowUpdate.ToString()), + new(Constants.TOKEN_ALLOW_CREATE, token.AllowCreate.ToString()), + new(Constants.TOKEN_ALLOW_DELETE, token.AllowDelete.ToString()), + }; + var claims = token.User.DefaultClaims().Concat(permissions); + var identity = new ClaimsIdentity(claims, Constants.BASIC_AUTH_SCHEME); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Constants.BASIC_AUTH_SCHEME); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } catch (Exception e) { + _logger.LogError(e, $"An exception occured when challenging {Constants.BASIC_AUTH_SCHEME}"); return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); } } diff --git a/src/server/Utilities/SnakeCaseNamingPolicy.cs b/src/server/Utilities/SnakeCaseNamingPolicy.cs new file mode 100644 index 0000000..9a7f1f3 --- /dev/null +++ b/src/server/Utilities/SnakeCaseNamingPolicy.cs @@ -0,0 +1,15 @@ +namespace IOL.BookmarkThing.Server.Utilities; + +public class SnakeCaseNamingPolicy : JsonNamingPolicy +{ + public static SnakeCaseNamingPolicy Instance { get; } = new SnakeCaseNamingPolicy(); + + public override string ConvertName(string name) { + // Conversion to other naming convention goes here. Like SnakeCase, KebabCase etc. + return ToSnakeCase(name); + } + + private static string ToSnakeCase(string str) { + return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower(); + } +} -- cgit v1.3