2024-09-10 16:53:43 +02:00
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using Foxnouns.Backend.Database;
|
2024-10-01 21:25:51 +02:00
|
|
|
using Foxnouns.Backend.Middleware;
|
2024-09-10 16:53:43 +02:00
|
|
|
using Foxnouns.Backend.Utils;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
|
|
using Microsoft.AspNetCore.Mvc.Routing;
|
|
|
|
using Microsoft.AspNetCore.Routing.Template;
|
2024-10-01 21:25:51 +02:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
2024-09-10 16:53:43 +02:00
|
|
|
|
|
|
|
namespace Foxnouns.Backend.Controllers;
|
|
|
|
|
|
|
|
[ApiController]
|
|
|
|
[Route("/api/internal")]
|
2024-10-01 21:25:51 +02:00
|
|
|
public partial class InternalController(ILogger logger, DatabaseContext db) : ControllerBase
|
2024-09-10 16:53:43 +02:00
|
|
|
{
|
2024-10-01 21:25:51 +02:00
|
|
|
private readonly ILogger _logger = logger.ForContext<InternalController>();
|
|
|
|
|
|
|
|
[HttpPost("force-log-out")]
|
|
|
|
[Authenticate]
|
|
|
|
[Authorize("identify")]
|
|
|
|
public async Task<IActionResult> ForceLogoutAsync()
|
|
|
|
{
|
|
|
|
var user = HttpContext.GetUser()!;
|
|
|
|
|
|
|
|
_logger.Information("Invalidating all tokens for user {UserId}", user.Id);
|
|
|
|
await db.Tokens.Where(t => t.UserId == user.Id)
|
|
|
|
.ExecuteUpdateAsync(s => s.SetProperty(t => t.ManuallyExpired, true));
|
|
|
|
|
|
|
|
return NoContent();
|
|
|
|
}
|
|
|
|
|
2024-09-10 16:53:43 +02:00
|
|
|
[GeneratedRegex(@"(\{\w+\})")]
|
|
|
|
private static partial Regex PathVarRegex();
|
|
|
|
|
|
|
|
private static string GetCleanedTemplate(string template)
|
|
|
|
{
|
2024-09-14 16:37:52 +02:00
|
|
|
if (template.StartsWith("api/v2")) template = template["api/v2".Length..];
|
2024-09-10 16:53:43 +02:00
|
|
|
template = PathVarRegex()
|
|
|
|
.Replace(template, "{id}") // Replace all path variables (almost always IDs) with `{id}`
|
|
|
|
.Replace("@me", "{id}"); // Also replace hardcoded `@me` with `{id}`
|
|
|
|
if (template.Contains("{id}")) return template.Split("{id}")[0] + "{id}";
|
|
|
|
return template;
|
|
|
|
}
|
|
|
|
|
|
|
|
[HttpPost("request-data")]
|
|
|
|
public async Task<IActionResult> GetRequestDataAsync([FromBody] RequestDataRequest req)
|
|
|
|
{
|
|
|
|
var endpoint = GetEndpoint(HttpContext, req.Path, req.Method);
|
|
|
|
if (endpoint == null) throw new ApiError.BadRequest("Path/method combination is invalid");
|
|
|
|
|
|
|
|
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
|
|
|
|
var template = actionDescriptor?.AttributeRouteInfo?.Template;
|
|
|
|
if (template == null) throw new FoxnounsError("Template value was null on valid endpoint");
|
|
|
|
template = GetCleanedTemplate(template);
|
|
|
|
|
|
|
|
// If no token was supplied, or it isn't valid base 64, return a null user ID (limiting by IP)
|
2024-09-11 16:23:45 +02:00
|
|
|
if (!AuthUtils.TryParseToken(req.Token, out var rawToken))
|
2024-09-10 16:53:43 +02:00
|
|
|
return Ok(new RequestDataResponse(null, template));
|
|
|
|
|
2024-09-11 16:34:08 +02:00
|
|
|
var userId = await db.GetTokenUserId(rawToken);
|
|
|
|
return Ok(new RequestDataResponse(userId, template));
|
2024-09-10 16:53:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public record RequestDataRequest(string? Token, string Method, string Path);
|
|
|
|
|
|
|
|
public record RequestDataResponse(
|
|
|
|
Snowflake? UserId,
|
|
|
|
string Template);
|
|
|
|
|
2024-09-14 16:37:52 +02:00
|
|
|
private static RouteEndpoint? GetEndpoint(HttpContext httpContext, string url, string requestMethod)
|
2024-09-10 16:53:43 +02:00
|
|
|
{
|
|
|
|
var endpointDataSource = httpContext.RequestServices.GetService<EndpointDataSource>();
|
|
|
|
if (endpointDataSource == null) return null;
|
|
|
|
var endpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>();
|
|
|
|
|
|
|
|
foreach (var endpoint in endpoints)
|
|
|
|
{
|
|
|
|
if (endpoint.RoutePattern.RawText == null) continue;
|
|
|
|
|
2024-09-25 19:48:05 +02:00
|
|
|
var templateMatcher = new TemplateMatcher(TemplateParser.Parse(endpoint.RoutePattern.RawText),
|
|
|
|
new RouteValueDictionary());
|
2024-09-10 16:53:43 +02:00
|
|
|
if (!templateMatcher.TryMatch(url, new())) continue;
|
|
|
|
var httpMethodAttribute = endpoint.Metadata.GetMetadata<HttpMethodAttribute>();
|
|
|
|
if (httpMethodAttribute != null &&
|
|
|
|
!httpMethodAttribute.HttpMethods.Any(x => x.Equals(requestMethod, StringComparison.OrdinalIgnoreCase)))
|
|
|
|
continue;
|
|
|
|
return endpoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|