diff --git a/extra/admin-api/Spacebar.AdminAPI/Controllers/UserController.cs b/extra/admin-api/Spacebar.AdminAPI/Controllers/UserController.cs
new file mode 100644
index 000000000..dbe497df6
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Controllers/UserController.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Spacebar.Db.Contexts;
+using Spacebar.Db.Models;
+
+namespace Spacebar.AdminAPI.Controllers;
+
+[ApiController]
+[Route("/users")]
+public class UserController(ILogger<UserController> logger, SpacebarDbContext db) : ControllerBase {
+ private readonly ILogger<UserController> _logger = logger;
+
+ [HttpGet(Name = "/")]
+ public IAsyncEnumerable<User> Get() {
+ return db.Users.AsAsyncEnumerable();
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.AdminAPI/Extensions/DbExtensions.cs b/extra/admin-api/Spacebar.AdminAPI/Extensions/DbExtensions.cs
new file mode 100644
index 000000000..c97a15759
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Extensions/DbExtensions.cs
@@ -0,0 +1,8 @@
+using Microsoft.EntityFrameworkCore;
+using Spacebar.Db.Models;
+
+namespace Spacebar.AdminAPI.Extensions;
+
+public static class DbExtensions {
+ public static string? GetString(this DbSet<Config> config, string key) => config.Find(key)?.Value;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.AdminAPI/Middleware/AuthenticationMiddleware.cs b/extra/admin-api/Spacebar.AdminAPI/Middleware/AuthenticationMiddleware.cs
new file mode 100644
index 000000000..400928e51
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Middleware/AuthenticationMiddleware.cs
@@ -0,0 +1,53 @@
+using System.Buffers.Text;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Cryptography;
+using System.Text;
+using ArcaneLibs.Extensions;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.IdentityModel.Tokens;
+using Spacebar.AdminAPI.Extensions;
+using Spacebar.Db.Contexts;
+
+namespace Spacebar.AdminAPI.Middleware;
+
+public class AuthenticationMiddleware(RequestDelegate next) {
+ public async Task Invoke(HttpContext context) {
+ if(Environment.GetEnvironmentVariable("SB_ADMIN_API_DISABLE_AUTH") == "true") {
+ await next(context);
+ return;
+ }
+
+ if (!context.Request.Headers.ContainsKey("Authorization")) {
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsync("Authorization header is missing");
+ return;
+ }
+
+ var token = context.Request.Headers["Authorization"].ToString().Split(' ').Last();
+
+ var handler = new JwtSecurityTokenHandler();
+ var secretFile = File.ReadAllText("../../../jwt.key.pub");
+ var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+ key.ImportFromPem(secretFile);
+
+ var res = await handler.ValidateTokenAsync(token, new TokenValidationParameters {
+ IssuerSigningKey = new ECDsaSecurityKey(key),
+ ValidAlgorithms = new[] { "ES512" },
+ LogValidationExceptions = true,
+ // These are required to be false for the token to be valid as they aren't provided by the token
+ ValidateIssuer = false,
+ ValidateLifetime = false,
+ ValidateAudience = false,
+ });
+
+ if (!res.IsValid) {
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsync("Invalid token");
+ return;
+ }
+
+ Console.WriteLine(res.ClaimsIdentity.Claims.Select(x => $"{x.Type} : {x.Value}").ToJson());
+
+ await next(context);
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.AdminAPI/Program.cs b/extra/admin-api/Spacebar.AdminAPI/Program.cs
new file mode 100644
index 000000000..8e40e3c54
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Program.cs
@@ -0,0 +1,58 @@
+using System.Text.Json.Serialization;
+using Microsoft.AspNetCore.Http.Timeouts;
+using Microsoft.EntityFrameworkCore;
+using Spacebar.AdminAPI.Middleware;
+using Spacebar.Db.Contexts;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers(options => {
+ options.MaxValidationDepth = null;
+ options.MaxIAsyncEnumerableBufferLimit = 100;
+}).AddJsonOptions(options => {
+ options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
+ options.JsonSerializerOptions.WriteIndented = true;
+});
+
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+builder.Services.AddOpenApi();
+builder.Services.AddDbContextPool<SpacebarDbContext>(options => {
+ options
+ .UseNpgsql(builder.Configuration.GetConnectionString("Spacebar"))
+ .EnableDetailedErrors();
+});
+
+builder.Services.AddRequestTimeouts(x => {
+ x.DefaultPolicy = new RequestTimeoutPolicy {
+ Timeout = TimeSpan.FromMinutes(10),
+ WriteTimeoutResponse = async context => {
+ context.Response.StatusCode = 504;
+ context.Response.ContentType = "application/json";
+ await context.Response.StartAsync();
+ await context.Response.WriteAsJsonAsync(new { error = "Unknown error" });
+ await context.Response.CompleteAsync();
+ }
+ };
+});
+builder.Services.AddCors(options => {
+ options.AddPolicy(
+ "Open",
+ policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
+});
+
+var app = builder.Build();
+app.UseCors("Open");
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment()) {
+ app.MapOpenApi();
+}
+
+app.UseMiddleware<AuthenticationMiddleware>();
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.AdminAPI/Properties/launchSettings.json b/extra/admin-api/Spacebar.AdminAPI/Properties/launchSettings.json
new file mode 100644
index 000000000..d362517ca
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Development": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5112",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Local": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5112",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Local"
+ }
+ }
+ }
+}
diff --git a/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.csproj b/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.csproj
new file mode 100644
index 000000000..8d14d79e9
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net9.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20241210-161342" />
+ <PackageReference Include="ArcaneLibs.StringNormalisation" Version="1.0.0-preview.20241210-161342" />
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
+ <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Spacebar.Db\Spacebar.Db.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.http b/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.http
new file mode 100644
index 000000000..707c9676d
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/Spacebar.AdminAPI.http
@@ -0,0 +1,6 @@
+@Spacebar.AdminAPI_HostAddress = http://localhost:5112
+
+GET {{Spacebar.AdminAPI_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/extra/admin-api/Spacebar.AdminAPI/appsettings.Development.json b/extra/admin-api/Spacebar.AdminAPI/appsettings.Development.json
new file mode 100644
index 000000000..5a367f27d
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/appsettings.Development.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "ConnectionStrings": {
+ "Spacebar": "Host=127.0.0.1; Username=postgres; Database=spacebar"
+ }
+}
diff --git a/extra/admin-api/Spacebar.AdminAPI/appsettings.json b/extra/admin-api/Spacebar.AdminAPI/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/extra/admin-api/Spacebar.AdminAPI/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
|