summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Controllers/ApiLoginController.cs46
-rw-r--r--Enum.cs5
-rw-r--r--Program.cs46
-rw-r--r--Server.csproj3
4 files changed, 100 insertions, 0 deletions
diff --git a/Controllers/ApiLoginController.cs b/Controllers/ApiLoginController.cs
new file mode 100644
index 0000000..fdf9bae
--- /dev/null
+++ b/Controllers/ApiLoginController.cs
@@ -0,0 +1,46 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+
+namespace HyperBooru.Controllers;
+
+[ApiController]
+[Route("/api/auth")]
+public class ApiLoginController : Controller {
+ private readonly RSA rsa;
+
+ public ApiLoginController(RSA rsa) =>
+ this.rsa = rsa;
+
+ [HttpPost]
+ public IActionResult Login([FromBody] LoginRequest request) {
+ var claims = new[] {
+ new Claim(ClaimTypes.Name, request.Username),
+ // TODO: Populate with the user's actual GUID
+ new Claim("uid", Guid.Empty.ToString().ToLower()),
+ new Claim("nsfw", request.NsfwClaim.ToString().ToLower())
+ };
+
+ var creds = new SigningCredentials(
+ new RsaSecurityKey(rsa),
+ SecurityAlgorithms.RsaSha256);
+
+ var token = new JwtSecurityToken(
+ claims: claims,
+ expires: DateTime.UtcNow.AddDays(30),
+ signingCredentials: creds);
+
+ var jwt = new JwtSecurityTokenHandler().WriteToken(token);
+
+ return Ok(new { token = jwt });
+ }
+
+ public record LoginRequest {
+ public required string Username { get; set; }
+ public required string Password { get; set; }
+
+ public bool NsfwClaim { get; set; } = false;
+ }
+}
diff --git a/Enum.cs b/Enum.cs
new file mode 100644
index 0000000..20e384c
--- /dev/null
+++ b/Enum.cs
@@ -0,0 +1,5 @@
+namespace HyperBooru.Server;
+
+public static class AuthorizationPolicy {
+ public const string NsfwPolicy = "IsNsfw";
+}
diff --git a/Program.cs b/Program.cs
index 1694587..2c56209 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,8 +1,11 @@
using HyperBooru.Server.Components;
using HyperBooru.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
+using System.Security.Cryptography;
using System.Text.Json.Serialization;
namespace HyperBooru.Server;
@@ -39,6 +42,47 @@ public class Program {
.GetRequiredService<IConfigService>()
.KeyPath));
+ // Load our persistently-stored JWT signing key
+ builder.Services.AddSingleton<RSA>(sp => {
+ var keyPath = Path.Combine(
+ sp.GetRequiredService<IConfigService>().KeyPath,
+ "jwt_key");
+
+ var protector = sp.GetRequiredService<IDataProtectionProvider>()
+ .CreateProtector("jwt-signing-key");
+
+ try {
+ var unprotected = protector.Unprotect(File.ReadAllBytes(keyPath));
+ var rsa = RSA.Create();
+ rsa.ImportRSAPrivateKey(unprotected, out var _);
+ return rsa;
+ } catch {
+ var rsa = RSA.Create(4096);
+ var privKey = rsa.ExportRSAPrivateKey();
+ File.WriteAllBytes(keyPath, protector.Protect(privKey));
+ return rsa;
+ }
+ });
+
+ // Configure JWT token-based authentication
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(o => {
+ var rsa = builder.Services.BuildServiceProvider().GetRequiredService<RSA>();
+ o.TokenValidationParameters = new TokenValidationParameters {
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new RsaSecurityKey(rsa)
+ };
+ });
+
+ // Configure custom authorization policies
+ builder.Services.AddAuthorization(o => {
+ o.AddPolicy(AuthorizationPolicy.NsfwPolicy, p => {
+ p.RequireClaim("nsfw", "true");
+ });
+ });
+
var app = builder.Build();
// Ensure database is created and it's schema is up to date
@@ -52,6 +96,8 @@ public class Program {
app.UseExceptionHandler("/Error");
}
+ app.UseAuthentication();
+ app.UseAuthorization();
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHsts();
app.UseHttpsRedirection();
diff --git a/Server.csproj b/Server.csproj
index 5188548..eb636a3 100644
--- a/Server.csproj
+++ b/Server.csproj
@@ -38,6 +38,7 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.8" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.14.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
@@ -45,10 +46,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+ <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
<PackageReference Include="Mime-Detective" Version="25.8.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.1" />
<PackageReference Include="System.Drawing.Common" Version="10.0.8" />
+ <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
<PackageReference Include="Tesseract" Version="5.2.0" />
</ItemGroup>