mirror of
https://github.com/spacebarchat/server.git
synced 2026-03-30 18:15:41 +00:00
Abstract out C# auth
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Spacebar.Interop.Authentication\Spacebar.Interop.Authentication.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Spacebar.Models.Db.Models;
|
||||
|
||||
namespace Spacebar.Interop.Authentication.AspNetCore;
|
||||
|
||||
public class SpacebarAspNetAuthenticationService(SpacebarAuthenticationService authService) {
|
||||
public string GetTokenAsync(HttpRequest request) {
|
||||
if (!request.Headers.ContainsKey("Authorization")) {
|
||||
Console.WriteLine(string.Join(", ", request.Headers.Keys));
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
return request.Headers["Authorization"].ToString().Split(' ').Last();
|
||||
}
|
||||
|
||||
public async Task<TokenValidationResult?> ValidateTokenAsync(HttpRequest request) {
|
||||
var token = GetTokenAsync(request);
|
||||
return await authService.ValidateTokenAsync(token);
|
||||
}
|
||||
|
||||
public async Task<User> GetCurrentUserAsync(HttpRequest request) {
|
||||
var token = GetTokenAsync(request);
|
||||
return await authService.GetCurrentUserAsync(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Models\Spacebar.Models.Db\Spacebar.Models.Db.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ArcaneLibs" Version="1.0.1-preview.20260126-091403" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2"/>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Spacebar.Interop.Authentication;
|
||||
|
||||
public class SpacebarAuthenticationConfiguration {
|
||||
public SpacebarAuthenticationConfiguration(IConfiguration configuration) {
|
||||
configuration.GetRequiredSection("Spacebar").GetRequiredSection("Authentication").Bind(this);
|
||||
}
|
||||
|
||||
public required string PrivateKeyPath { get; set; }
|
||||
public required string PublicKeyPath { get; set; }
|
||||
|
||||
public string? OverrideUid { get; set; }
|
||||
public bool DisableAuthentication { get; set; } = false;
|
||||
public bool Enforce2FA { get; set; } = true;
|
||||
public TimeSpan AuthCacheExpiry { get; set; } = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using ArcaneLibs.Collections;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Spacebar.Models.Db.Contexts;
|
||||
using Spacebar.Models.Db.Models;
|
||||
|
||||
namespace Spacebar.Interop.Authentication;
|
||||
|
||||
public class SpacebarAuthenticationService(ILogger<SpacebarAuthenticationService> logger, SpacebarDbContext db, SpacebarAuthenticationConfiguration config) {
|
||||
private static readonly ExpiringSemaphoreCache<User> UserCache = new();
|
||||
|
||||
public async Task<TokenValidationResult?> ValidateTokenAsync(string token) {
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var secretFile = await File.ReadAllTextAsync(config.PublicKeyPath);
|
||||
var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
key.ImportFromPem(secretFile);
|
||||
|
||||
var res = await handler.ValidateTokenAsync(token, new TokenValidationParameters {
|
||||
IssuerSigningKey = new ECDsaSecurityKey(key),
|
||||
ValidAlgorithms = ["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,
|
||||
// TryAllIssuerSigningKeys = true
|
||||
});
|
||||
|
||||
if ((!res.IsValid || res.Exception is not null) && !config.DisableAuthentication) {
|
||||
logger.LogInformation("Invalid token");
|
||||
throw res.Exception ?? new UnauthorizedAccessException("Token was invalid");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public async Task<User> GetCurrentUserAsync(string token) {
|
||||
var res = await ValidateTokenAsync(token);
|
||||
return await UserCache.GetOrAdd(token,
|
||||
async () => {
|
||||
var uid = config.OverrideUid ?? res?.ClaimsIdentity.Claims.First(x => x.Type == "id").Value;
|
||||
if (string.IsNullOrWhiteSpace(uid)) throw new InvalidOperationException("No user ID specified, is the access token valid?");
|
||||
return await db.Users.FindAsync(uid) ?? throw new InvalidOperationException();
|
||||
},
|
||||
config.AuthCacheExpiry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
[
|
||||
{
|
||||
"pname": "Microsoft.EntityFrameworkCore",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-xfgrlxhtOkQwF5Q7j8gSm41URJiH8IuJ/T/Dh88++hE="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.EntityFrameworkCore.Abstractions",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-UDgZbRQcGPaKsE53EH6bvJiv+Q4KSxAbnsVhTVFGG4Q="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.EntityFrameworkCore.Analyzers",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-7Q0jYJO50cqGI+u6gLpootbB8GZvgsgtg0F9FZI1jig="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.EntityFrameworkCore.Relational",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-vOP2CE5YA551BlpbOuIy6RuAiAEPEpCVS1cEE33/zN4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Caching.Abstractions",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-IciARPnXx/S6HZc4t2ED06UyUwfZI9LKSzwKSGdpsfI="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Caching.Memory",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-AMgDSm1k6q0s17spGtyR5q8nAqUFDOxl/Fe38f9M+d4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Configuration",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-dBJAKDyp/sm+ZSMQfH0+4OH8Jnv1s20aHlWS6HNnH+c="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Configuration.Abstractions",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-GcgrnTAieCV7AVT13zyOjfwwL86e99iiO/MiMOxPGG0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Configuration.Abstractions",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-P+0kaDGO+xB9KxF9eWHDJ4hzi05sUGM/uMNEX5NdBTE="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Configuration.Binder",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-resI9gIxHh2cc+258/i+TjW8xxzKf4ZBTLIcWAMEYz0="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.DependencyInjection",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-LYm9hVlo/R9c2aAKHsDYJ5vY9U0+3Jvclme3ou3BtvQ="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.DependencyInjection",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-/9UWQRAI2eoocnJWWf1ktnAx/1Gt65c16fc0Xqr9+CQ="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-9iodXP39YqgxomnOPOxd/mzbG0JfOSXzFoNU3omT2Ps="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-UF9T13V5SQxJy2msfLmyovLmitZrjJayf8gHH+uK2eg="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Logging",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-P+zPAadLL63k/GqK34/qChqQjY9aIRxZfxlB9lqsSrs="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Logging.Abstractions",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-BnhgGZc01HwTSxogavq7Ueq4V7iMA3wPnbfRwQ4RhGk="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Options",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-j5MOqZSKeUtxxzmZjzZMGy0vELHdvPraqwTQQQNVsYA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Primitives",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-Dup08KcptLjlnpN5t5//+p4n8FUTgRAq4n/w1s6us+I="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.Extensions.Primitives",
|
||||
"version": "10.0.2",
|
||||
"hash": "sha256-8Ccrjjv9cFVf9RyCc7GS/Byt8+DXdSNea0UX3A5BEdA="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.IdentityModel.Abstractions",
|
||||
"version": "8.15.0",
|
||||
"hash": "sha256-LKTvERNUTMCEF7xs377tCMwOMRki93OS4kh6Yv0uXJ4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.IdentityModel.JsonWebTokens",
|
||||
"version": "8.15.0",
|
||||
"hash": "sha256-LwzKiGjcnORvmQ9tim6lomXpfVlPpd/fE8FKTFWKlpM="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.IdentityModel.Logging",
|
||||
"version": "8.15.0",
|
||||
"hash": "sha256-mMXwsjGcrrmHR1mG7BLTKg/30mX+m93QVX17/ynOOd4="
|
||||
},
|
||||
{
|
||||
"pname": "Microsoft.IdentityModel.Tokens",
|
||||
"version": "8.15.0",
|
||||
"hash": "sha256-7Lo/TsvqgNCEMyFssO3Om233521Pqgb9K9lUeHc5HMk="
|
||||
},
|
||||
{
|
||||
"pname": "Npgsql",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-UVKz9dH/rVCCbMyFdqA31RYpht1XgDRLIqUy0Dp9ACQ="
|
||||
},
|
||||
{
|
||||
"pname": "Npgsql.EntityFrameworkCore.PostgreSQL",
|
||||
"version": "10.0.0",
|
||||
"hash": "sha256-XIJxnTMektQVP1qtslEIGbmBGrIQsvjQjCMRTs9UIbg="
|
||||
},
|
||||
{
|
||||
"pname": "System.IdentityModel.Tokens.Jwt",
|
||||
"version": "8.15.0",
|
||||
"hash": "sha256-5O0wbGp0gWnukK+0mWBjMnP1bZc6N0xuNcO2qmFiUX8="
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user