Implemented apikey backend
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
|
||||
namespace Moonlight.ApiServer.Helpers.Authentication;
|
||||
|
||||
public class PermClaimsPrinciple : ClaimsPrincipal
|
||||
{
|
||||
public string[] Permissions { get; private set; }
|
||||
public User? CurrentModelNullable { get; private set; }
|
||||
public User CurrentModel => CurrentModelNullable!;
|
||||
|
||||
public PermClaimsPrinciple(string[] permissions, User? currentModelNullable)
|
||||
{
|
||||
Permissions = permissions;
|
||||
CurrentModelNullable = currentModelNullable;
|
||||
}
|
||||
|
||||
public bool HasPermission(string requiredPermission)
|
||||
{
|
||||
// Check for wildcard permission
|
||||
if (Permissions.Contains("*"))
|
||||
return true;
|
||||
|
||||
var requiredSegments = requiredPermission.Split('.');
|
||||
|
||||
// Check if the user has the exact permission or a wildcard match
|
||||
foreach (var permission in Permissions)
|
||||
{
|
||||
var permissionSegments = permission.Split('.');
|
||||
|
||||
// Iterate over the segments of the required permission
|
||||
for (var i = 0; i < requiredSegments.Length; i++)
|
||||
{
|
||||
// If the current segment matches or is a wildcard, continue to the next segment
|
||||
if (i < permissionSegments.Length && requiredSegments[i] == permissionSegments[i] ||
|
||||
permissionSegments[i] == "*")
|
||||
{
|
||||
// If we've reached the end of the permissionSegments array, it means we've found a match
|
||||
if (i == permissionSegments.Length - 1)
|
||||
return true; // Found an exact match or a wildcard match
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we reach here, it means the segments don't match and we break out of the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No matching permission found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Moonlight.ApiServer.Helpers;
|
||||
using Moonlight.ApiServer.Models;
|
||||
|
||||
namespace Moonlight.ApiServer.Interfaces.Startup;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MoonCore" Version="1.6.7" />
|
||||
<PackageReference Include="MoonCore" Version="1.6.8" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.1.3" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.2" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||
|
||||
@@ -158,6 +158,8 @@ foreach (var startupInterface in appStartupInterfaces)
|
||||
}
|
||||
}
|
||||
|
||||
app.UseMiddleware<ApiAuthenticationMiddleware>();
|
||||
|
||||
app.UseMiddleware<AuthorizationMiddleware>();
|
||||
|
||||
// Call interfaces
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using MoonCore.Authentication;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Extended.Extensions;
|
||||
using MoonCore.Extended.Helpers;
|
||||
@@ -7,7 +8,6 @@ using MoonCore.Helpers;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Helpers;
|
||||
using Moonlight.ApiServer.Helpers.Authentication;
|
||||
using Moonlight.ApiServer.Interfaces.Startup;
|
||||
|
||||
namespace Moonlight.ApiServer;
|
||||
@@ -79,7 +79,10 @@ public static class Startup
|
||||
) ?? [];
|
||||
|
||||
// Save permission state
|
||||
context.User = new PermClaimsPrinciple(permissions, user);
|
||||
context.User = new PermClaimsPrinciple(permissions)
|
||||
{
|
||||
IdentityModel = user
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Moonlight.Client.UI.Layouts;
|
||||
|
||||
namespace Moonlight.Client.Interfaces;
|
||||
|
||||
public interface IAppLoader
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all"/>
|
||||
<PackageReference Include="MoonCore" Version="1.6.7" />
|
||||
<PackageReference Include="MoonCore" Version="1.6.8" />
|
||||
<PackageReference Include="MoonCore.Blazor" Version="1.2.5" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.2" />
|
||||
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.0.9" />
|
||||
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,8 +11,6 @@ using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using MoonCore.PluginFramework.Extensions;
|
||||
using MoonCore.PluginFramework.Services;
|
||||
using Moonlight.Client.Implementations;
|
||||
using Moonlight.Client.Interfaces;
|
||||
using Moonlight.Client.Services;
|
||||
using Moonlight.Client.UI;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Blazor.Services;
|
||||
using MoonCore.Blazor.Tailwind.Services;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using Moonlight.Shared.Http.Requests.Auth;
|
||||
using Moonlight.Shared.Http.Responses.Auth;
|
||||
|
||||
namespace Moonlight.Client.Services;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
@using MoonCore.Exceptions
|
||||
@using MoonCore.Extensions
|
||||
@using MoonCore.Helpers
|
||||
@using MoonCore.PluginFramework.Services
|
||||
@using Moonlight.Client.Interfaces
|
||||
@using Moonlight.Client.Services
|
||||
@using Moonlight.Client.UI.Partials
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using MoonCore.PluginFramework.Services
|
||||
@using Moonlight.Client.Interfaces
|
||||
@using Moonlight.Client.Interfaces
|
||||
@using Moonlight.Client.Models
|
||||
@using Moonlight.Client.Services
|
||||
@using Moonlight.Client.UI.Layouts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/admin/api"
|
||||
|
||||
@using MoonCore.Blazor.Tailwind.Attributes
|
||||
@using MoonCore.Attributes
|
||||
@using MoonCore.Helpers
|
||||
@using MoonCore.Models
|
||||
@using Moonlight.Shared.Http.Requests.Admin.ApiKeys
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/admin/system"
|
||||
|
||||
@using MoonCore.Blazor.Tailwind.Attributes
|
||||
@using MoonCore.Attributes
|
||||
@using Moonlight.Client.UI.Components
|
||||
|
||||
@attribute [RequirePermission("admin.system.read")]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/admin/users"
|
||||
|
||||
@using MoonCore.Blazor.Tailwind.Attributes
|
||||
@using MoonCore.Attributes
|
||||
@using MoonCore.Blazor.Tailwind.Forms.Components
|
||||
@using MoonCore.Helpers
|
||||
@using MoonCore.Models
|
||||
|
||||
Reference in New Issue
Block a user