mirror of
https://github.com/spacebarchat/server.git
synced 2026-08-29 09:49:13 +00:00
Local changes
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Spacebar.AdminAPI.Services;
|
||||
|
||||
namespace Spacebar.AdminAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/")]
|
||||
public class PingController(ILogger<PingController> logger, IServiceProvider sp, AuthenticationService auth) : ControllerBase {
|
||||
private readonly ILogger<PingController> _logger = logger;
|
||||
|
||||
[HttpGet("ping")]
|
||||
public async Task<object> Ping() {
|
||||
return new {
|
||||
ok = true
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("whoami")]
|
||||
public async Task<object> WhoAmI() {
|
||||
var user = await auth.GetCurrentUser(Request);
|
||||
return new {
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.Discriminator,
|
||||
user.Bot,
|
||||
user.Flags,
|
||||
user.Rights,
|
||||
user.MfaEnabled,
|
||||
user.WebauthnEnabled,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,404 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using ArcaneLibs.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RabbitMQ.Client;
|
||||
using Spacebar.AdminApi.Models;
|
||||
using Spacebar.Db.Contexts;
|
||||
using Spacebar.Db.Models;
|
||||
using Spacebar.RabbitMqUtilities;
|
||||
|
||||
namespace Spacebar.AdminAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("/users")]
|
||||
public class UserController(ILogger<UserController> logger, SpacebarDbContext db) : ControllerBase {
|
||||
[Route("/Users")]
|
||||
public class UserController(ILogger<UserController> logger, SpacebarDbContext db, RabbitMQService mq, IServiceProvider sp) : ControllerBase {
|
||||
private readonly ILogger<UserController> _logger = logger;
|
||||
|
||||
[HttpGet(Name = "/")]
|
||||
public IAsyncEnumerable<User> Get() {
|
||||
return db.Users.AsAsyncEnumerable();
|
||||
[HttpGet]
|
||||
public IAsyncEnumerable<UserModel> Get() {
|
||||
return db.Users.Select(x => new UserModel {
|
||||
Id = x.Id,
|
||||
Username = x.Username,
|
||||
Discriminator = x.Discriminator,
|
||||
Avatar = x.Avatar,
|
||||
AccentColor = x.AccentColor,
|
||||
Banner = x.Banner,
|
||||
ThemeColors = x.ThemeColors,
|
||||
Pronouns = x.Pronouns,
|
||||
Phone = x.Phone,
|
||||
Desktop = x.Desktop,
|
||||
Mobile = x.Mobile,
|
||||
Premium = x.Premium,
|
||||
PremiumType = x.PremiumType,
|
||||
Bot = x.Bot,
|
||||
Bio = x.Bio,
|
||||
System = x.System,
|
||||
NsfwAllowed = x.NsfwAllowed,
|
||||
MfaEnabled = x.MfaEnabled,
|
||||
WebauthnEnabled = x.WebauthnEnabled,
|
||||
CreatedAt = x.CreatedAt,
|
||||
PremiumSince = x.PremiumSince,
|
||||
Verified = x.Verified,
|
||||
Disabled = x.Disabled,
|
||||
Deleted = x.Deleted,
|
||||
Email = x.Email,
|
||||
Flags = x.Flags,
|
||||
PublicFlags = x.PublicFlags,
|
||||
Rights = x.Rights,
|
||||
ApplicationBotUser = x.ApplicationBotUser == null ? null : new() { },
|
||||
ConnectedAccounts = new List<UserModel.ConnectedAccountModel>(),
|
||||
MessageCount = x.MessageAuthors.Count, // This property is weirdly named due to scaffolding, might patch later
|
||||
SessionCount = x.Sessions.Count,
|
||||
TemplateCount = x.Templates.Count,
|
||||
VoiceStateCount = x.VoiceStates.Count,
|
||||
GuildCount = x.Guilds.Count,
|
||||
OwnedGuildCount = x.Guilds.Count(g => g.OwnerId == x.Id)
|
||||
}).AsAsyncEnumerable();
|
||||
}
|
||||
|
||||
[HttpGet("meow")]
|
||||
public async Task Meow() {
|
||||
Console.WriteLine("meow");
|
||||
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
factory.Uri = new Uri("amqp://guest:guest@127.0.0.1/");
|
||||
using var connection = await factory.CreateConnectionAsync();
|
||||
using var channel = await connection.CreateChannelAsync();
|
||||
|
||||
// await using var channel = mq.CreateChannel();
|
||||
// var channel2 = await channel.CreateChannelAsync();
|
||||
|
||||
var body =
|
||||
$$"""
|
||||
{
|
||||
"id": "{{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}}",
|
||||
"channel_id": "1322343566206308390",
|
||||
"guild_id": "1322343566084673571",
|
||||
"author": {
|
||||
"username": "test",
|
||||
"discriminator": "9177",
|
||||
"id": "1322329228934500382",
|
||||
"public_flags": 0,
|
||||
"avatar": null,
|
||||
"accent_color": null,
|
||||
"banner": null,
|
||||
"bio": "",
|
||||
"bot": false,
|
||||
"premium_since": "2024-12-27T22:24:15.867Z",
|
||||
"premium_type": 2,
|
||||
"theme_colors": null,
|
||||
"pronouns": null,
|
||||
"badge_ids": null
|
||||
},
|
||||
"member": {
|
||||
"index": 2,
|
||||
"id": "1322329228934500382",
|
||||
"guild_id": "1322343566084673571",
|
||||
"nick": null,
|
||||
"joined_at": "2024-12-27T23:21:14.396Z",
|
||||
"premium_since": null,
|
||||
"deaf": false,
|
||||
"mute": false,
|
||||
"pending": false,
|
||||
"last_message_id": "1322346635635753061",
|
||||
"joined_by": null,
|
||||
"avatar": null,
|
||||
"banner": null,
|
||||
"bio": "",
|
||||
"theme_colors": null,
|
||||
"pronouns": null,
|
||||
"communication_disabled_until": null,
|
||||
"roles": []
|
||||
},
|
||||
"content": "{{Random.Shared.NextInt64()}}",
|
||||
"timestamp": "{{DateTime.UtcNow:O}}",
|
||||
"edited_timestamp": null,
|
||||
"tts": false,
|
||||
"mention_everyone": false,
|
||||
"mentions": [],
|
||||
"mention_roles": [],
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"reactions": [],
|
||||
"nonce": "{{Random.Shared.NextInt64()}}",
|
||||
"pinned": false,
|
||||
"type": 0
|
||||
}
|
||||
"""
|
||||
.AsBytes().ToArray();
|
||||
|
||||
await channel.ExchangeDeclareAsync(exchange: "1322343566206308390", type: ExchangeType.Fanout, durable: false);
|
||||
var props = new BasicProperties() { Type = "MESSAGE_CREATE" };
|
||||
await channel.BasicPublishAsync(exchange: "1322343566206308390", routingKey: "", mandatory: true, basicProperties: props, body: body);
|
||||
|
||||
await channel.CloseAsync();
|
||||
await connection.CloseAsync();
|
||||
Console.WriteLine("meowww");
|
||||
}
|
||||
|
||||
[HttpGet("{id}/delete")]
|
||||
public async IAsyncEnumerable<AsyncActionResult> DeleteUser(string id, [FromQuery] int messageDeleteChunkSize = 100) {
|
||||
var user = await db.Users.FindAsync(id);
|
||||
if (user == null) {
|
||||
Console.WriteLine($"User {id} not found");
|
||||
yield return new AsyncActionResult("ERROR", new { entity = "User", id, message = "User not found" });
|
||||
yield break;
|
||||
}
|
||||
|
||||
var factory = new ConnectionFactory {
|
||||
Uri = new Uri("amqp://guest:guest@127.0.0.1/")
|
||||
};
|
||||
await using var mqConnection = await factory.CreateConnectionAsync();
|
||||
await using var mqChannel = await mqConnection.CreateChannelAsync();
|
||||
|
||||
var messages = db.Messages
|
||||
.AsNoTracking()
|
||||
.Where(m => m.AuthorId == id);
|
||||
var channels = messages
|
||||
.Select(m => new { m.ChannelId, m.GuildId })
|
||||
.Distinct()
|
||||
.ToList();
|
||||
yield return new("STATS",
|
||||
new {
|
||||
total_messages = messages.Count(), total_channels = channels.Count,
|
||||
messages_per_channel = channels.ToDictionary(c => c.ChannelId, c => messages.Count(m => m.ChannelId == c.ChannelId))
|
||||
});
|
||||
var results = channels
|
||||
.Select(ctx => DeleteMessagesForChannel(ctx.GuildId, ctx.ChannelId!, id, mqChannel, messageDeleteChunkSize))
|
||||
.ToList();
|
||||
var a = AggregateAsyncEnumerablesWithoutOrder(results);
|
||||
await foreach (var result in a) {
|
||||
yield return result;
|
||||
}
|
||||
|
||||
await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages");
|
||||
await db.Database.ExecuteSqlRawAsync("REINDEX TABLE messages");
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<AsyncActionResult> DeleteMessagesForChannel(
|
||||
// context
|
||||
string? guildId, string channelId, string authorId,
|
||||
// connections
|
||||
IChannel mqChannel,
|
||||
// options
|
||||
int messageDeleteChunkSize = 100
|
||||
) {
|
||||
{
|
||||
await using var ctx = sp.CreateAsyncScope();
|
||||
await using var _db = ctx.ServiceProvider.GetRequiredService<SpacebarDbContext>();
|
||||
await mqChannel.ExchangeDeclareAsync(exchange: channelId!, type: ExchangeType.Fanout, durable: false);
|
||||
var messagesInChannel = _db.Messages.AsNoTracking().Count(m => m.AuthorId == authorId && m.ChannelId == channelId && m.GuildId == guildId);
|
||||
var remaining = messagesInChannel;
|
||||
while (true) {
|
||||
var messageIds = _db.Database.SqlQuery<string>($"""
|
||||
DELETE FROM messages
|
||||
WHERE id IN (
|
||||
SELECT id FROM messages
|
||||
WHERE author_id = {authorId}
|
||||
AND channel_id = {channelId}
|
||||
AND guild_id = {guildId}
|
||||
LIMIT {messageDeleteChunkSize}
|
||||
) RETURNING id;
|
||||
""").ToList();
|
||||
if (messageIds.Count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
var props = new BasicProperties() { Type = "MESSAGE_BULK_DELETE" };
|
||||
var publishSuccess = false;
|
||||
do {
|
||||
try {
|
||||
await mqChannel.BasicPublishAsync(exchange: channelId!, routingKey: "", mandatory: true, basicProperties: props, body: new {
|
||||
ids = messageIds,
|
||||
channel_id = channelId,
|
||||
guild_id = guildId,
|
||||
}.ToJson().AsBytes().ToArray());
|
||||
publishSuccess = true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Console.WriteLine($"[RabbitMQ] Error publishing bulk delete: {e.Message}");
|
||||
await Task.Delay(10);
|
||||
}
|
||||
} while (!publishSuccess);
|
||||
|
||||
yield return new("BULK_DELETED", new {
|
||||
channel_id = channelId,
|
||||
total = messagesInChannel,
|
||||
deleted = messageIds.Count,
|
||||
remaining = remaining -= messageIds.Count,
|
||||
});
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("duplicate")]
|
||||
public async Task<IActionResult> Duplicate() {
|
||||
var msg = db.Messages.First();
|
||||
var channels = db.Channels.Select(x => new { x.Id, x.GuildId }).ToList();
|
||||
int count = 1;
|
||||
while (true) {
|
||||
foreach (var channel in channels) {
|
||||
var newMsg = new Message {
|
||||
Id = $"{Random.Shared.NextInt64()}",
|
||||
ChannelId = channel.Id,
|
||||
GuildId = channel.GuildId,
|
||||
AuthorId = msg.AuthorId,
|
||||
Content = msg.Content,
|
||||
MemberId = msg.MemberId,
|
||||
Timestamp = msg.Timestamp,
|
||||
EditedTimestamp = msg.EditedTimestamp,
|
||||
Tts = msg.Tts,
|
||||
MentionEveryone = msg.MentionEveryone,
|
||||
Attachments = msg.Attachments,
|
||||
Embeds = msg.Embeds,
|
||||
Reactions = msg.Reactions,
|
||||
Nonce = msg.Nonce,
|
||||
Pinned = msg.Pinned,
|
||||
Type = msg.Type,
|
||||
};
|
||||
db.Messages.Add(newMsg);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count % 100 == 0) {
|
||||
await db.SaveChangesAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages");
|
||||
}
|
||||
|
||||
if (count >= 100_000) {
|
||||
await db.SaveChangesAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages");
|
||||
await db.Database.ExecuteSqlRawAsync("REINDEX TABLE messages");
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("duplicate/{id}")]
|
||||
public async Task<IActionResult> DuplicateMessage(ulong id, [FromQuery] int count = 100) {
|
||||
var msg = await db.Messages.FindAsync(id.ToString());
|
||||
int createdCount = 1;
|
||||
while (true) {
|
||||
var newMsg = new Message {
|
||||
Id = $"{Random.Shared.NextInt64()}",
|
||||
ChannelId = msg.ChannelId,
|
||||
GuildId = msg.GuildId,
|
||||
AuthorId = msg.AuthorId,
|
||||
Content = msg.Content,
|
||||
MemberId = msg.MemberId,
|
||||
Timestamp = msg.Timestamp,
|
||||
EditedTimestamp = msg.EditedTimestamp,
|
||||
Tts = msg.Tts,
|
||||
MentionEveryone = msg.MentionEveryone,
|
||||
Attachments = msg.Attachments,
|
||||
Embeds = msg.Embeds,
|
||||
Reactions = msg.Reactions,
|
||||
Nonce = msg.Nonce,
|
||||
Pinned = msg.Pinned,
|
||||
Type = msg.Type,
|
||||
};
|
||||
db.Messages.Add(newMsg);
|
||||
createdCount++;
|
||||
|
||||
if (createdCount % 100 == 0) {
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (createdCount >= count) {
|
||||
await db.SaveChangesAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages");
|
||||
await db.Database.ExecuteSqlRawAsync("REINDEX TABLE messages");
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("VACUUM FULL messages");
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("truncate_messages")]
|
||||
public async Task TruncateMessages() {
|
||||
var channels = db.Channels.Select(x => new { x.Id, x.GuildId }).ToList();
|
||||
|
||||
var ss = new SemaphoreSlim(12, 12);
|
||||
|
||||
async Task TruncateChannelMessages(string channelId, string guildId) {
|
||||
await ss.WaitAsync();
|
||||
var tasks = Enumerable.Range(0, 99).Select(i => Task.Run(async () => {
|
||||
await using var scope = sp.CreateAsyncScope();
|
||||
await using var _db = scope.ServiceProvider.GetRequiredService<SpacebarDbContext>();
|
||||
// set timeout
|
||||
_db.Database.SetCommandTimeout(6000);
|
||||
await _db.Database.ExecuteSqlRawAsync($"""
|
||||
DELETE FROM messages
|
||||
WHERE channel_id = '{channelId}'
|
||||
AND guild_id = '{guildId}'
|
||||
AND id LIKE '%{i:00}';
|
||||
""");
|
||||
|
||||
Console.WriteLine($"Truncated messages for {channelId} in {guildId} ending with {i}");
|
||||
})).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
ss.Release();
|
||||
}
|
||||
|
||||
var tasks = channels.Select(c => TruncateChannelMessages(c.Id, c.GuildId)).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<T> AggregateAsyncEnumerablesWithoutOrder<T>(params IEnumerable<IAsyncEnumerable<T>> enumerables) {
|
||||
var enumerators = enumerables.Select(e => e.GetAsyncEnumerator()).ToList();
|
||||
var tasks = enumerators.Select(e => e.MoveNextAsync().AsTask()).ToList();
|
||||
|
||||
try {
|
||||
while (tasks.Count > 0) {
|
||||
var completedTask = await Task.WhenAny(tasks);
|
||||
var completedTaskIndex = tasks.IndexOf(completedTask);
|
||||
|
||||
if (completedTask.IsCanceled) {
|
||||
try {
|
||||
await enumerators[completedTaskIndex].DisposeAsync();
|
||||
}
|
||||
catch {
|
||||
// ignored
|
||||
}
|
||||
|
||||
enumerators.RemoveAt(completedTaskIndex);
|
||||
tasks.RemoveAt(completedTaskIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await completedTask) {
|
||||
var enumerator = enumerators[completedTaskIndex];
|
||||
yield return enumerator.Current;
|
||||
tasks[completedTaskIndex] = enumerator.MoveNextAsync().AsTask();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
await enumerators[completedTaskIndex].DisposeAsync();
|
||||
}
|
||||
catch {
|
||||
// ignored
|
||||
}
|
||||
|
||||
enumerators.RemoveAt(completedTaskIndex);
|
||||
tasks.RemoveAt(completedTaskIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
foreach (var enumerator in enumerators) {
|
||||
try {
|
||||
await enumerator.DisposeAsync();
|
||||
}
|
||||
catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
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;
|
||||
using Spacebar.Db.Models;
|
||||
|
||||
namespace Spacebar.AdminAPI.Middleware;
|
||||
|
||||
public class AuthenticationMiddleware(RequestDelegate next) {
|
||||
public async Task Invoke(HttpContext context) {
|
||||
if(Environment.GetEnvironmentVariable("SB_ADMIN_API_DISABLE_AUTH") == "true") {
|
||||
private static Dictionary<string, User> _userCache = new();
|
||||
private static Dictionary<string, DateTime> _userCacheExpiry = new();
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IServiceProvider sp) {
|
||||
if (context.Request.Path.StartsWithSegments("/ping")) {
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!context.Request.Headers.ContainsKey("Authorization")) {
|
||||
context.Response.StatusCode = 401;
|
||||
await context.Response.WriteAsync("Authorization header is missing");
|
||||
@@ -46,8 +46,33 @@ public class AuthenticationMiddleware(RequestDelegate next) {
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(res.ClaimsIdentity.Claims.Select(x => $"{x.Type} : {x.Value}").ToJson());
|
||||
User user;
|
||||
if (_userCacheExpiry.ContainsKey(token) && _userCacheExpiry[token] < DateTime.Now) {
|
||||
_userCache.Remove(token);
|
||||
_userCacheExpiry.Remove(token);
|
||||
}
|
||||
|
||||
if (!_userCache.ContainsKey(token)) {
|
||||
var db = sp.GetRequiredService<SpacebarDbContext>();
|
||||
user = await db.Users.FindAsync(res.ClaimsIdentity.Claims.First(x => x.Type == "id").Value)
|
||||
?? throw new InvalidOperationException();
|
||||
_userCache[token] = user;
|
||||
_userCacheExpiry[token] = DateTime.Now.AddMinutes(5);
|
||||
}
|
||||
|
||||
user = _userCache[token];
|
||||
if (user.Disabled) {
|
||||
context.Response.StatusCode = 403;
|
||||
await context.Response.WriteAsync("User is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.Deleted) {
|
||||
context.Response.StatusCode = 403;
|
||||
await context.Response.WriteAsync("User is deleted");
|
||||
return;
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Http.Timeouts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Spacebar.AdminAPI.Middleware;
|
||||
using Spacebar.AdminAPI.Services;
|
||||
using Spacebar.Db.Contexts;
|
||||
using Spacebar.RabbitMqUtilities;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -10,10 +12,13 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddControllers(options => {
|
||||
options.MaxValidationDepth = null;
|
||||
options.MaxIAsyncEnumerableBufferLimit = 100;
|
||||
// options.MaxIAsyncEnumerableBufferLimit = 1;
|
||||
}).AddJsonOptions(options => {
|
||||
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
||||
options.JsonSerializerOptions.WriteIndented = true;
|
||||
// options.JsonSerializerOptions.DefaultBufferSize = ;
|
||||
}).AddMvcOptions(o=> {
|
||||
o.SuppressOutputFormatterBuffering = true;
|
||||
});
|
||||
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
@@ -23,6 +28,10 @@ builder.Services.AddDbContextPool<SpacebarDbContext>(options => {
|
||||
.UseNpgsql(builder.Configuration.GetConnectionString("Spacebar"))
|
||||
.EnableDetailedErrors();
|
||||
});
|
||||
builder.Services.AddScoped<AuthenticationService>();
|
||||
builder.Services.AddScoped<Configuration>();
|
||||
builder.Services.AddSingleton<RabbitMQConfiguration>();
|
||||
builder.Services.AddSingleton<RabbitMQService>();
|
||||
|
||||
builder.Services.AddRequestTimeouts(x => {
|
||||
x.DefaultPolicy = new RequestTimeoutPolicy {
|
||||
@@ -43,6 +52,7 @@ builder.Services.AddCors(options => {
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
app.UsePathBase("/_spacebar/admin");
|
||||
app.UseCors("Open");
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using ArcaneLibs.Extensions;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Spacebar.Db.Contexts;
|
||||
using Spacebar.Db.Models;
|
||||
|
||||
namespace Spacebar.AdminAPI.Services;
|
||||
|
||||
public class AuthenticationService(SpacebarDbContext db) {
|
||||
private static Dictionary<string, User> _userCache = new();
|
||||
private static Dictionary<string, DateTime> _userCacheExpiry = new();
|
||||
|
||||
public async Task<User> GetCurrentUser(HttpRequest request) {
|
||||
if (!request.Headers.ContainsKey("Authorization")) {
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var token = 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) {
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
return await db.Users.FindAsync(res.ClaimsIdentity.Claims.First(x => x.Type == "id").Value) ?? throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Spacebar.AdminAPI.Services;
|
||||
|
||||
public class Configuration {
|
||||
public Configuration(IConfiguration configuration) {
|
||||
configuration.GetRequiredSection("SpacebarAdminApi").Bind(this);
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,14 @@
|
||||
<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="RabbitMQ.Client" Version="7.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Spacebar.AdminApi.Models\Spacebar.AdminApi.Models.csproj" />
|
||||
<ProjectReference Include="..\Spacebar.Db\Spacebar.Db.csproj" />
|
||||
<ProjectReference Include="..\Utilities\Spacebar.RabbitMqUtilities\Spacebar.RabbitMqUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
@Spacebar.AdminAPI_HostAddress = http://localhost:5112
|
||||
|
||||
GET {{Spacebar.AdminAPI_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
Reference in New Issue
Block a user