Abstract out C# auth
1 files changed, 49 insertions, 0 deletions
diff --git a/extra/admin-api/Interop/Spacebar.Interop.Authentication/SpacebarAuthenticationService.cs b/extra/admin-api/Interop/Spacebar.Interop.Authentication/SpacebarAuthenticationService.cs
new file mode 100644
index 00000000..7b4ad1c8
--- /dev/null
+++ b/extra/admin-api/Interop/Spacebar.Interop.Authentication/SpacebarAuthenticationService.cs
@@ -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);
+ }
+}
\ No newline at end of file
|