Implemented apikey backend

This commit is contained in:
Masu-Baumgartner
2024-10-30 13:34:19 +01:00
parent 6d0c75ceff
commit fce44f49b6
20 changed files with 90 additions and 260 deletions

View File

@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using MoonCore.Blazor.Tailwind.Attributes;
using MoonCore.Attributes;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;

View File

@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using MoonCore.Blazor.Tailwind.Attributes;
using MoonCore.Attributes;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;

View File

@@ -1,15 +1,16 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using MoonCore.Blazor.Tailwind.Attributes;
using MoonCore.Attributes;
using MoonCore.Authentication;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Extended.OAuth2.ApiServer;
using MoonCore.Extensions;
using MoonCore.Helpers;
using MoonCore.Services;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Helpers.Authentication;
using Moonlight.ApiServer.Interfaces.Auth;
using Moonlight.ApiServer.Interfaces.OAuth2;
using Moonlight.Shared.Http.Requests.Auth;
@@ -210,14 +211,14 @@ public class AuthController : Controller
[RequirePermission("meta.authenticated")]
public Task<CheckResponse> Check()
{
var perm = HttpContext.User as PermClaimsPrinciple;
var user = perm!.CurrentModel;
var permClaim = (HttpContext.User as PermClaimsPrinciple)!;
var user = (User)permClaim.IdentityModel;
var response = new CheckResponse()
{
Email = user.Email,
Username = user.Username,
Permissions = perm.Permissions
Permissions = permClaim.Permissions
};
return Task.FromResult(response);

View File

@@ -0,0 +1,64 @@
using System.Text.Json;
using MoonCore.Authentication;
using MoonCore.Extended.Abstractions;
using Moonlight.ApiServer.Database.Entities;
namespace Moonlight.ApiServer.Http.Middleware;
public class ApiAuthenticationMiddleware
{
private readonly RequestDelegate Next;
private readonly ILogger<ApiAuthenticationMiddleware> Logger;
public ApiAuthenticationMiddleware(RequestDelegate next, ILogger<ApiAuthenticationMiddleware> logger)
{
Next = next;
Logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
await Authenticate(context);
await Next(context);
}
public Task Authenticate(HttpContext context)
{
var request = context.Request;
if(!request.Headers.ContainsKey("Authorization"))
return Task.CompletedTask;
if(request.Headers["Authorization"].Count == 0)
return Task.CompletedTask;
var authHeader = request.Headers["Authorization"].First();
if(string.IsNullOrEmpty(authHeader))
return Task.CompletedTask;
var parts = authHeader.Split(" ");
if(parts.Length != 2)
return Task.CompletedTask;
var bearerValue = parts[1];
if(!bearerValue.StartsWith("api_"))
return Task.CompletedTask;
if(bearerValue.Length != "api_".Length + 32)
return Task.CompletedTask;
var apiKeyRepo = context.RequestServices.GetRequiredService<DatabaseRepository<ApiKey>>();
var apiKey = apiKeyRepo.Get().FirstOrDefault(x => x.Secret == bearerValue);
if(apiKey == null)
return Task.CompletedTask;
var permissions = JsonSerializer.Deserialize<string[]>(apiKey.PermissionsJson) ?? [];
context.User = new PermClaimsPrinciple(permissions);
return Task.CompletedTask;
}
}

View File

@@ -1,176 +0,0 @@
namespace Moonlight.ApiServer.Http.Middleware;
public class AuthenticationMiddleware
{
private readonly RequestDelegate Next;
private readonly ILogger<AuthenticationMiddleware> Logger;
public AuthenticationMiddleware(RequestDelegate next, ILogger<AuthenticationMiddleware> logger)
{
Next = next;
Logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
//await Authenticate(context);
await Next(context);
}
/*
private async Task Authenticate(HttpContext context)
{
var request = context.Request;
if (!request.Cookies.TryGetValue("ml-access", out var accessToken) ||
!request.Cookies.TryGetValue("ml-refresh", out var refreshToken))
return;
if(string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
return;
// TODO: Validate if both are valid jwts (maybe)
//
var tokenHelper = context.RequestServices.GetRequiredService<TokenHelper>();
var configService = context.RequestServices.GetRequiredService<ConfigService<AppConfiguration>>();
User? user = null;
if (!await tokenHelper.IsValidAccessToken(accessToken, configService.Get().Authentication.MlAccessSecret,
data =>
{
if (!data.TryGetValue("userId", out var userIdStr))
return false;
if (!int.TryParse(userIdStr, out var userId))
return false;
var userRepo = context.RequestServices.GetRequiredService<DatabaseRepository<User>>();
user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
return user != null;
}))
{
return;
}
if(user == null)
return;
// Validate external access
if (DateTime.UtcNow > user.RefreshTimestamp)
{
var tokenConsumer = new TokenConsumer(user.AccessToken, user.RefreshToken, user.RefreshTimestamp,
async refreshToken =>
{
var oauth2Service = context.RequestServices.GetRequiredService<OAuth2Service>();
var accessData = await oauth2Service.RefreshAccess(refreshToken);
user.AccessToken = accessData.AccessToken;
user.RefreshToken = accessData.RefreshToken;
user.RefreshTimestamp = DateTime.UtcNow.AddSeconds(accessData.ExpiresIn);
var userRepo = context.RequestServices.GetRequiredService<DatabaseRepository<User>>();
userRepo.Update(user);
return new TokenPair()
{
AccessToken = user.AccessToken,
RefreshToken = user.RefreshToken
};
});
await tokenConsumer.GetAccessToken();
//TODO: API CALL
}
// Load permissions, handle empty values
var permissions = JsonSerializer.Deserialize<string[]>(
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
) ?? [];
// Save permission state
context.User = new PermClaimsPrinciple(permissions, user);
/// IGNORE
string? token = null;
// Cookie for Moonlight.Client
if (request.Cookies.ContainsKey("token") && !string.IsNullOrEmpty(request.Cookies["token"]))
token = request.Cookies["token"];
// Header for api clients
if (request.Headers.ContainsKey("Authorization") && !string.IsNullOrEmpty(request.Cookies["Authorization"]))
{
var headerValue = request.Cookies["Authorization"] ?? "";
if (headerValue.StartsWith("Bearer"))
{
var headerParts = headerValue.Split(" ");
if (headerParts.Length > 1 && !string.IsNullOrEmpty(headerParts[1]))
token = headerParts[1];
}
}
if(token == null)
return;
// Validate token
if (token.Length > 300)
{
Logger.LogWarning("Received token bigger than 300 characters, Length: {length}", token.Length);
return;
}
// Decide which authentication method we need for the token
if (token.Count(x => x == '.') == 2) // JWT only has two dots
await AuthenticateUser(context, token);
else
await AuthenticateApiKey(context, token);
}
private async Task AuthenticateUser(HttpContext context, string jwt)
{
var jwtHelper = context.RequestServices.GetRequiredService<JwtHelper>();
var configService = context.RequestServices.GetRequiredService<ConfigService<AppConfiguration>>();
var secret = configService.Get().Authentication.Secret;
if (!await jwtHelper.Validate(secret, jwt, "login"))
return;
var data = await jwtHelper.Decode(secret, jwt);
if (!data.TryGetValue("iat", out var issuedAtString) || !data.TryGetValue("userId", out var userIdString))
return;
var userId = int.Parse(userIdString);
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedAtString)).DateTime;
var userRepo = context.RequestServices.GetRequiredService<DatabaseRepository<User>>();
var user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
if (user == null)
return;
// Check if token is in the past
if (user.TokenValidTimestamp > issuedAt)
return;
// Load permissions, handle empty values
var permissions = JsonSerializer.Deserialize<string[]>(
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
) ?? [];
// Save permission state
context.User = new PermClaimsPrinciple(permissions, user);
}
private async Task AuthenticateApiKey(HttpContext context, string apiKey)
{
}
*/
}

View File

@@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using MoonCore.Blazor.Tailwind.Attributes;
using MoonCore.Attributes;
using MoonCore.Authentication;
using MoonCore.Extensions;
using Moonlight.ApiServer.Exceptions;
using Moonlight.ApiServer.Helpers.Authentication;
namespace Moonlight.ApiServer.Http.Middleware;
@@ -64,7 +65,7 @@ public class AuthorizationMiddleware
}
// Check if one of the required permissions is to be logged in
if (requiredPermissions.Any(x => x == "meta.authenticated") && permClaimsPrinciple.CurrentModelNullable == null)
if (requiredPermissions.Any(x => x == "meta.authenticated") && permClaimsPrinciple.IdentityModel == null)
{
await Results.Problem(
title: "This endpoint requires a user authenticated token",