add lodestone auth, add rate limiting middleware
This commit is contained in:
@@ -15,6 +15,7 @@ namespace MareSynchronosServer.Data
|
|||||||
public DbSet<ForbiddenUploadEntry> ForbiddenUploadEntries { get; set; }
|
public DbSet<ForbiddenUploadEntry> ForbiddenUploadEntries { get; set; }
|
||||||
public DbSet<Banned> BannedUsers { get; set; }
|
public DbSet<Banned> BannedUsers { get; set; }
|
||||||
public DbSet<Auth> Auth { get; set; }
|
public DbSet<Auth> Auth { get; set; }
|
||||||
|
public DbSet<LodeStoneAuth> LodeStoneAuth { get; set; }
|
||||||
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
@@ -30,6 +31,7 @@ namespace MareSynchronosServer.Data
|
|||||||
modelBuilder.Entity<ClientPair>().HasIndex(c => c.OtherUserUID);
|
modelBuilder.Entity<ClientPair>().HasIndex(c => c.OtherUserUID);
|
||||||
modelBuilder.Entity<ForbiddenUploadEntry>().ToTable("forbidden_upload_entries");
|
modelBuilder.Entity<ForbiddenUploadEntry>().ToTable("forbidden_upload_entries");
|
||||||
modelBuilder.Entity<Banned>().ToTable("banned_users");
|
modelBuilder.Entity<Banned>().ToTable("banned_users");
|
||||||
|
modelBuilder.Entity<LodeStoneAuth>().ToTable("lodestone_auth");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
331
MareSynchronosServer/MareSynchronosServer/Discord/DiscordBot.cs
Normal file
331
MareSynchronosServer/MareSynchronosServer/Discord/DiscordBot.cs
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using MareSynchronosServer.Data;
|
||||||
|
using MareSynchronosServer.Hubs;
|
||||||
|
using MareSynchronosServer.Metrics;
|
||||||
|
using MareSynchronosServer.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Discord
|
||||||
|
{
|
||||||
|
public class DiscordBot : IHostedService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider services;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
private readonly ILogger<DiscordBot> logger;
|
||||||
|
private string authToken = string.Empty;
|
||||||
|
DiscordSocketClient discordClient;
|
||||||
|
ConcurrentDictionary<ulong, string> DiscordLodestoneMapping = new();
|
||||||
|
private Timer _timer;
|
||||||
|
private readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "na" };
|
||||||
|
|
||||||
|
public DiscordBot(IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
|
||||||
|
{
|
||||||
|
this.services = services;
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
discordClient = new(new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
DefaultRetryMode = RetryMode.AlwaysRetry
|
||||||
|
});
|
||||||
|
|
||||||
|
discordClient.Log += Log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DiscordClient_SlashCommandExecuted(SocketSlashCommand arg)
|
||||||
|
{
|
||||||
|
if (arg.Data.Name == "register")
|
||||||
|
{
|
||||||
|
var modal = new ModalBuilder();
|
||||||
|
modal.WithTitle("Verify with Lodestone");
|
||||||
|
modal.WithCustomId("register_modal");
|
||||||
|
modal.AddTextInput("Enter the Lodestone URL of your Character", "lodestoneurl", TextInputStyle.Short, "https://*.finalfantasyxiv.com/lodestone/character/<CHARACTERID>/", required: true);
|
||||||
|
await arg.RespondWithModalAsync(modal.Build());
|
||||||
|
}
|
||||||
|
else if (arg.Data.Name == "verify")
|
||||||
|
{
|
||||||
|
await arg.DeferAsync(true);
|
||||||
|
Embed response = await HandleVerifyAsync(arg.User.Id);
|
||||||
|
await arg.FollowupAsync(embeds: new[] { response });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DiscordClient_ModalSubmitted(SocketModal arg)
|
||||||
|
{
|
||||||
|
if (arg.Data.CustomId == "register_modal")
|
||||||
|
{
|
||||||
|
await arg.DeferAsync(true);
|
||||||
|
var embed = await HandleRegisterModalAsync(arg);
|
||||||
|
await arg.FollowupAsync(embeds: new Embed[] { embed }, ephemeral: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Embed> HandleVerifyAsync(ulong id)
|
||||||
|
{
|
||||||
|
var embedBuilder = new EmbedBuilder();
|
||||||
|
if (!DiscordLodestoneMapping.ContainsKey(id))
|
||||||
|
{
|
||||||
|
embedBuilder.WithTitle("Cannot verify registration");
|
||||||
|
embedBuilder.WithDescription("You need to **/register** first before you can **/verify**");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var scope = services.CreateScope();
|
||||||
|
var req = new HttpClient();
|
||||||
|
using var db = scope.ServiceProvider.GetService<MareDbContext>();
|
||||||
|
|
||||||
|
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == id);
|
||||||
|
if (lodestoneAuth != null)
|
||||||
|
{
|
||||||
|
Random rand = new();
|
||||||
|
var randomServer = LodestoneServers[rand.Next(LodestoneServers.Length)];
|
||||||
|
var response = await req.GetAsync($"https://{randomServer}.finalfantasyxiv.com/lodestone/character/{DiscordLodestoneMapping[id]}");
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
if (content.Contains(lodestoneAuth.LodestoneAuthString))
|
||||||
|
{
|
||||||
|
DiscordLodestoneMapping.TryRemove(id, out _);
|
||||||
|
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var user = new User();
|
||||||
|
|
||||||
|
var hasValidUid = false;
|
||||||
|
while (!hasValidUid)
|
||||||
|
{
|
||||||
|
var uid = MareHub.GenerateRandomString(10);
|
||||||
|
if (db.Users.Any(u => u.UID == uid)) continue;
|
||||||
|
user.UID = uid;
|
||||||
|
hasValidUid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make the first registered user on the service to admin
|
||||||
|
if (!await db.Users.AnyAsync())
|
||||||
|
{
|
||||||
|
user.IsAdmin = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("PurgeUnusedAccounts"))
|
||||||
|
{
|
||||||
|
var purgedDays = configuration.GetValue<int>("PurgeUnusedAccountsPeriodInDays");
|
||||||
|
user.LastLoggedIn = DateTime.UtcNow - TimeSpan.FromDays(purgedDays) + TimeSpan.FromHours(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var computedHash = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(MareHub.GenerateRandomString(64)))).Replace("-", "");
|
||||||
|
var auth = new Auth()
|
||||||
|
{
|
||||||
|
HashedKey = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(computedHash)))
|
||||||
|
.Replace("-", ""),
|
||||||
|
User = user,
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Users.Add(user);
|
||||||
|
db.Auth.Add(auth);
|
||||||
|
|
||||||
|
logger.LogInformation("User registered: " + user.UID);
|
||||||
|
|
||||||
|
MareMetrics.UsersRegistered.Inc();
|
||||||
|
|
||||||
|
lodestoneAuth.StartedAt = null;
|
||||||
|
lodestoneAuth.User = user;
|
||||||
|
lodestoneAuth.LodestoneAuthString = null;
|
||||||
|
|
||||||
|
embedBuilder.WithTitle("Registration successful");
|
||||||
|
embedBuilder.WithDescription("This is your private secret key. Do not share this private secret key with anyone. If you lose it, it is irrevocably lost."
|
||||||
|
+ Environment.NewLine + Environment.NewLine
|
||||||
|
+ $"**{computedHash}**"
|
||||||
|
+ Environment.NewLine + Environment.NewLine
|
||||||
|
+ "Enter this key in Mare Synchronos and hit save to connect to the service."
|
||||||
|
+ Environment.NewLine
|
||||||
|
+ "You should connect as soon as possible to not get caught by the automatic cleanup process."
|
||||||
|
+ Environment.NewLine
|
||||||
|
+ "Have fun.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
embedBuilder.WithTitle("Failed to verify your character");
|
||||||
|
embedBuilder.WithDescription("Did not find requested authentication key on your profile. Start over with **/register**");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
embedBuilder.WithTitle("Failed to get response from Lodestone");
|
||||||
|
embedBuilder.WithDescription("Try again later");
|
||||||
|
lodestoneAuth.StartedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
embedBuilder.WithTitle("Your auth has expired");
|
||||||
|
embedBuilder.WithDescription("Start again with **/register**");
|
||||||
|
DiscordLodestoneMapping.TryRemove(id, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return embedBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Embed> HandleRegisterModalAsync(SocketModal arg)
|
||||||
|
{
|
||||||
|
var embed = new EmbedBuilder();
|
||||||
|
|
||||||
|
var lodestoneId = ParseCharacterIdFromLodestoneUrl(arg.Data.Components.Single(c => c.CustomId == "lodestoneurl").Value);
|
||||||
|
if (lodestoneId == null)
|
||||||
|
{
|
||||||
|
embed.WithTitle("Invalid Lodestone URL");
|
||||||
|
embed.WithDescription("The lodestone URL was not valid. It should have following format:" + Environment.NewLine
|
||||||
|
+ "https://eu.finalfantasyxiv.com/lodestone/character/YOUR_LODESTONE_ID/");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// check if userid is already in db
|
||||||
|
using var scope = services.CreateScope();
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
|
||||||
|
var hashedLodestoneId = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(lodestoneId.ToString()))).Replace("-", "");
|
||||||
|
|
||||||
|
var db = scope.ServiceProvider.GetService<MareDbContext>();
|
||||||
|
|
||||||
|
if (db.LodeStoneAuth.Any(a => a.DiscordId == arg.User.Id))
|
||||||
|
{
|
||||||
|
// user already in db
|
||||||
|
embed.WithTitle("Registration failed");
|
||||||
|
embed.WithDescription("You cannot register more than one lodestone character to your discord account.");
|
||||||
|
}
|
||||||
|
else if (db.LodeStoneAuth.Any(a => a.HashedLodestoneId == hashedLodestoneId))
|
||||||
|
{
|
||||||
|
// character already in db
|
||||||
|
embed.WithTitle("Registration failed");
|
||||||
|
embed.WithDescription("This lodestone character already exists in the Database. If you are the rightful owner for this character and lost your secret key generated with it, contact the developer.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string lodestoneAuth = await GenerateLodestoneAuth(arg.User.Id, hashedLodestoneId, db);
|
||||||
|
// check if lodestone id is already in db
|
||||||
|
embed.WithTitle("Authorize your character");
|
||||||
|
embed.WithDescription("Add following key to your character profile at https://na.finalfantasyxiv.com/lodestone/my/setting/profile/"
|
||||||
|
+ Environment.NewLine + Environment.NewLine
|
||||||
|
+ $"**{lodestoneAuth}**"
|
||||||
|
+ Environment.NewLine + Environment.NewLine
|
||||||
|
+ "Once added and saved, use command **/verify** to finish registration and receive a secret key to use for Mare Synchronos."
|
||||||
|
+ Environment.NewLine
|
||||||
|
+ "You can delete the entry from your profile after verification."
|
||||||
|
+ Environment.NewLine + Environment.NewLine
|
||||||
|
+ "The verification will expire in approximately 15 minutes. If you fail to **/verify** the registration will be invalidated and you have to **/register** again.");
|
||||||
|
DiscordLodestoneMapping[arg.User.Id] = lodestoneId.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateLodestoneAuth(ulong discordid, string hashedLodestoneId, MareDbContext dbContext)
|
||||||
|
{
|
||||||
|
var auth = MareHub.GenerateRandomString(64);
|
||||||
|
LodeStoneAuth lsAuth = new LodeStoneAuth()
|
||||||
|
{
|
||||||
|
DiscordId = discordid,
|
||||||
|
HashedLodestoneId = hashedLodestoneId,
|
||||||
|
LodestoneAuthString = auth,
|
||||||
|
StartedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
dbContext.Add(lsAuth);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? ParseCharacterIdFromLodestoneUrl(string lodestoneUrl)
|
||||||
|
{
|
||||||
|
var isLodestoneUrl = Regex.Match(lodestoneUrl, @"https:\/\/(na|eu|de|fr|jp)\.finalfantasyxiv\.com\/lodestone\/character\/\d+").Success;
|
||||||
|
if (!isLodestoneUrl) return null;
|
||||||
|
|
||||||
|
lodestoneUrl = lodestoneUrl.Split('/', StringSplitOptions.RemoveEmptyEntries).Last();
|
||||||
|
if (!int.TryParse(lodestoneUrl, out int lodestoneId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lodestoneId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DiscordClient_Ready()
|
||||||
|
{
|
||||||
|
var cb = new SlashCommandBuilder();
|
||||||
|
cb.WithName("register");
|
||||||
|
cb.WithDescription("Starts the registration process for the Mare Synchronos server of this Discord");
|
||||||
|
|
||||||
|
var cb2 = new SlashCommandBuilder();
|
||||||
|
cb2.WithName("verify");
|
||||||
|
cb2.WithDescription("Finishes the registration process for the Mare Synchronos server of this Discord");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await discordClient.CreateGlobalApplicationCommandAsync(cb.Build());
|
||||||
|
await discordClient.CreateGlobalApplicationCommandAsync(cb2.Build());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Failed to create command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Log(LogMessage msg)
|
||||||
|
{
|
||||||
|
logger.LogInformation(msg.ToString());
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
authToken = configuration.GetValue<string>("DiscordBotToken");
|
||||||
|
if (!string.IsNullOrEmpty(authToken))
|
||||||
|
{
|
||||||
|
await discordClient.LoginAsync(TokenType.Bot, authToken);
|
||||||
|
await discordClient.StartAsync();
|
||||||
|
|
||||||
|
discordClient.Ready += DiscordClient_Ready;
|
||||||
|
discordClient.SlashCommandExecuted += DiscordClient_SlashCommandExecuted;
|
||||||
|
discordClient.ModalSubmitted += DiscordClient_ModalSubmitted;
|
||||||
|
|
||||||
|
_timer = new Timer(UpdateStatus, null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatus(object state)
|
||||||
|
{
|
||||||
|
using var scope = services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetService<MareDbContext>();
|
||||||
|
|
||||||
|
var users = db.Users.Count(c => c.CharacterIdentification != null);
|
||||||
|
|
||||||
|
discordClient.SetActivityAsync(new Game("Mare for " + users + " Users"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_timer?.Change(Timeout.Infinite, 0);
|
||||||
|
|
||||||
|
await discordClient.LogoutAsync();
|
||||||
|
await discordClient.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,20 @@ namespace MareSynchronosServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lodestoneAuths = dbContext.LodeStoneAuth.Include(u => u.User).Where(a => a.StartedAt != null).ToList();
|
||||||
|
List<LodeStoneAuth> expiredAuths = new List<LodeStoneAuth>();
|
||||||
|
foreach (var auth in lodestoneAuths)
|
||||||
|
{
|
||||||
|
if (auth.StartedAt < DateTime.UtcNow - TimeSpan.FromMinutes(15))
|
||||||
|
{
|
||||||
|
expiredAuths.Add(auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbContext.RemoveRange(expiredAuths);
|
||||||
|
dbContext.RemoveRange(expiredAuths.Select(a => a.User));
|
||||||
|
|
||||||
|
|
||||||
if (!bool.TryParse(_configuration["PurgeUnusedAccounts"], out var purgeUnusedAccounts))
|
if (!bool.TryParse(_configuration["PurgeUnusedAccounts"], out var purgeUnusedAccounts))
|
||||||
{
|
{
|
||||||
purgeUnusedAccounts = false;
|
purgeUnusedAccounts = false;
|
||||||
@@ -91,7 +105,7 @@ namespace MareSynchronosServer
|
|||||||
List<User> usersToRemove = new();
|
List<User> usersToRemove = new();
|
||||||
foreach (var user in allUsers)
|
foreach (var user in allUsers)
|
||||||
{
|
{
|
||||||
if (user.LastLoggedIn < (DateTime.Now - TimeSpan.FromDays(usersOlderThanDays)))
|
if (user.LastLoggedIn < (DateTime.UtcNow - TimeSpan.FromDays(usersOlderThanDays)))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("User outdated: " + user.UID);
|
_logger.LogInformation("User outdated: " + user.UID);
|
||||||
usersToRemove.Add(user);
|
usersToRemove.Add(user);
|
||||||
@@ -100,6 +114,13 @@ namespace MareSynchronosServer
|
|||||||
|
|
||||||
foreach (var user in usersToRemove)
|
foreach (var user in usersToRemove)
|
||||||
{
|
{
|
||||||
|
var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID);
|
||||||
|
|
||||||
|
if (lodestone != null)
|
||||||
|
{
|
||||||
|
dbContext.Remove(lodestone);
|
||||||
|
}
|
||||||
|
|
||||||
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
|
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
|
||||||
|
|
||||||
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();
|
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ namespace MareSynchronosServer.Hubs
|
|||||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == userid).ToListAsync();
|
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == userid).ToListAsync();
|
||||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid);
|
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid);
|
||||||
|
|
||||||
|
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == userid);
|
||||||
|
|
||||||
|
if (lodestone != null)
|
||||||
|
{
|
||||||
|
_dbContext.Remove(lodestone);
|
||||||
|
}
|
||||||
|
|
||||||
while (_dbContext.Files.Any(f => f.Uploader == userEntry))
|
while (_dbContext.Files.Any(f => f.Uploader == userEntry))
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
@@ -130,47 +137,6 @@ namespace MareSynchronosServer.Hubs
|
|||||||
MareMetrics.UserPushDataTo.Inc(visibleCharacterIds.Count);
|
MareMetrics.UserPushDataTo.Inc(visibleCharacterIds.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[HubMethodName(Api.InvokeUserRegister)]
|
|
||||||
public async Task<string> Register()
|
|
||||||
{
|
|
||||||
using var sha256 = SHA256.Create();
|
|
||||||
var user = new User();
|
|
||||||
|
|
||||||
var hasValidUid = false;
|
|
||||||
while (!hasValidUid)
|
|
||||||
{
|
|
||||||
var uid = GenerateRandomString(10);
|
|
||||||
if (_dbContext.Users.Any(u => u.UID == uid)) continue;
|
|
||||||
user.UID = uid;
|
|
||||||
hasValidUid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the first registered user on the service to admin
|
|
||||||
if (!await _dbContext.Users.AnyAsync())
|
|
||||||
{
|
|
||||||
user.IsAdmin = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var computedHash = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(GenerateRandomString(64)))).Replace("-", "");
|
|
||||||
var auth = new Auth()
|
|
||||||
{
|
|
||||||
HashedKey = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(computedHash)))
|
|
||||||
.Replace("-", ""),
|
|
||||||
User = user
|
|
||||||
};
|
|
||||||
|
|
||||||
_dbContext.Users.Add(user);
|
|
||||||
_dbContext.Auth.Add(auth);
|
|
||||||
|
|
||||||
_logger.LogInformation("User registered: " + user.UID);
|
|
||||||
|
|
||||||
MareMetrics.UsersRegistered.Inc();
|
|
||||||
|
|
||||||
await _dbContext.SaveChangesAsync();
|
|
||||||
return computedHash;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
[Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)]
|
[Authorize(AuthenticationSchemes = SecretKeyAuthenticationHandler.AuthScheme)]
|
||||||
[HubMethodName(Api.SendUserPairedClientAddition)]
|
[HubMethodName(Api.SendUserPairedClientAddition)]
|
||||||
public async Task SendPairedClientAddition(string uid)
|
public async Task SendPairedClientAddition(string uid)
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
|
||||||
<PackageReference Include="Bazinga.AspNetCore.Authentication.Basic" Version="2.0.1" />
|
<PackageReference Include="Bazinga.AspNetCore.Authentication.Basic" Version="2.0.1" />
|
||||||
|
<PackageReference Include="Discord.Net" Version="3.7.2" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
|
||||||
<PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.3.0" />
|
<PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.3.0" />
|
||||||
<PackageReference Include="lz4net" Version="1.0.15.93" />
|
<PackageReference Include="lz4net" Version="1.0.15.93" />
|
||||||
|
|||||||
283
MareSynchronosServer/MareSynchronosServer/Migrations/20220801121419_AddLodestoneAuth.Designer.cs
generated
Normal file
283
MareSynchronosServer/MareSynchronosServer/Migrations/20220801121419_AddLodestoneAuth.Designer.cs
generated
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using MareSynchronosServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MareDbContext))]
|
||||||
|
[Migration("20220801121419_AddLodestoneAuth")]
|
||||||
|
partial class AddLodestoneAuth
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "6.0.6")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Auth", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("HashedKey")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasColumnName("hashed_key");
|
||||||
|
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.HasKey("HashedKey")
|
||||||
|
.HasName("pk_auth");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_auth_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("auth", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Banned", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("CharacterIdentification")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("character_identification");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("CharacterIdentification")
|
||||||
|
.HasName("pk_banned_users");
|
||||||
|
|
||||||
|
b.ToTable("banned_users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ClientPair", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.Property<string>("OtherUserUID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("other_user_uid");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowReceivingMessages")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_receiving_messages");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPaused")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_paused");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("UserUID", "OtherUserUID")
|
||||||
|
.HasName("pk_client_pairs");
|
||||||
|
|
||||||
|
b.HasIndex("OtherUserUID")
|
||||||
|
.HasDatabaseName("ix_client_pairs_other_user_uid");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_client_pairs_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("client_pairs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.FileCache", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.Property<bool>("Uploaded")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("uploaded");
|
||||||
|
|
||||||
|
b.Property<string>("UploaderUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("uploader_uid");
|
||||||
|
|
||||||
|
b.HasKey("Hash")
|
||||||
|
.HasName("pk_file_caches");
|
||||||
|
|
||||||
|
b.HasIndex("UploaderUID")
|
||||||
|
.HasDatabaseName("ix_file_caches_uploader_uid");
|
||||||
|
|
||||||
|
b.ToTable("file_caches", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ForbiddenUploadEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<string>("ForbiddenBy")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("forbidden_by");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("Hash")
|
||||||
|
.HasName("pk_forbidden_upload_entries");
|
||||||
|
|
||||||
|
b.ToTable("forbidden_upload_entries", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("DiscordId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("discord_id");
|
||||||
|
|
||||||
|
b.Property<string>("HashedLodestoneId")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("hashed_lodestone_id");
|
||||||
|
|
||||||
|
b.Property<string>("LodestoneAuthString")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("lodestone_auth_string");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.HasKey("DiscordId")
|
||||||
|
.HasName("pk_lodestone_auth");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_lodestone_auth_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("lodestone_auth", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("uid");
|
||||||
|
|
||||||
|
b.Property<string>("CharacterIdentification")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("character_identification");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_admin");
|
||||||
|
|
||||||
|
b.Property<bool>("IsModerator")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_moderator");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastLoggedIn")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last_logged_in");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("UID")
|
||||||
|
.HasName("pk_users");
|
||||||
|
|
||||||
|
b.HasIndex("CharacterIdentification")
|
||||||
|
.HasDatabaseName("ix_users_character_identification");
|
||||||
|
|
||||||
|
b.ToTable("users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Auth", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.HasConstraintName("fk_auth_users_user_temp_id");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ClientPair", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "OtherUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OtherUserUID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_client_pairs_users_other_user_temp_id1");
|
||||||
|
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_client_pairs_users_user_temp_id2");
|
||||||
|
|
||||||
|
b.Navigation("OtherUser");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.FileCache", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "Uploader")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UploaderUID")
|
||||||
|
.HasConstraintName("fk_file_caches_users_uploader_uid");
|
||||||
|
|
||||||
|
b.Navigation("Uploader");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.HasConstraintName("fk_lodestone_auth_users_user_uid");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddLodestoneAuth : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "lodestone_auth",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
discord_id = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
hashed_lodestone_id = table.Column<string>(type: "text", nullable: true),
|
||||||
|
lodestone_auth_string = table.Column<string>(type: "text", nullable: true),
|
||||||
|
user_uid = table.Column<string>(type: "character varying(10)", nullable: true),
|
||||||
|
started_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_lodestone_auth", x => x.discord_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_lodestone_auth_users_user_uid",
|
||||||
|
column: x => x.user_uid,
|
||||||
|
principalTable: "users",
|
||||||
|
principalColumn: "uid");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_lodestone_auth_user_uid",
|
||||||
|
table: "lodestone_auth",
|
||||||
|
column: "user_uid");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "lodestone_auth");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using MareSynchronosServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MareDbContext))]
|
||||||
|
[Migration("20220801122103_AddNullableLodestoneAuthProperties")]
|
||||||
|
partial class AddNullableLodestoneAuthProperties
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "6.0.6")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Auth", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("HashedKey")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasColumnName("hashed_key");
|
||||||
|
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.HasKey("HashedKey")
|
||||||
|
.HasName("pk_auth");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_auth_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("auth", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Banned", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("CharacterIdentification")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("character_identification");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("CharacterIdentification")
|
||||||
|
.HasName("pk_banned_users");
|
||||||
|
|
||||||
|
b.ToTable("banned_users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ClientPair", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.Property<string>("OtherUserUID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("other_user_uid");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowReceivingMessages")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_receiving_messages");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPaused")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_paused");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("UserUID", "OtherUserUID")
|
||||||
|
.HasName("pk_client_pairs");
|
||||||
|
|
||||||
|
b.HasIndex("OtherUserUID")
|
||||||
|
.HasDatabaseName("ix_client_pairs_other_user_uid");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_client_pairs_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("client_pairs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.FileCache", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.Property<bool>("Uploaded")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("uploaded");
|
||||||
|
|
||||||
|
b.Property<string>("UploaderUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("uploader_uid");
|
||||||
|
|
||||||
|
b.HasKey("Hash")
|
||||||
|
.HasName("pk_file_caches");
|
||||||
|
|
||||||
|
b.HasIndex("UploaderUID")
|
||||||
|
.HasDatabaseName("ix_file_caches_uploader_uid");
|
||||||
|
|
||||||
|
b.ToTable("file_caches", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ForbiddenUploadEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(40)
|
||||||
|
.HasColumnType("character varying(40)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<string>("ForbiddenBy")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("forbidden_by");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("Hash")
|
||||||
|
.HasName("pk_forbidden_upload_entries");
|
||||||
|
|
||||||
|
b.ToTable("forbidden_upload_entries", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("DiscordId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("discord_id");
|
||||||
|
|
||||||
|
b.Property<string>("HashedLodestoneId")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("hashed_lodestone_id");
|
||||||
|
|
||||||
|
b.Property<string>("LodestoneAuthString")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("lodestone_auth_string");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.HasKey("DiscordId")
|
||||||
|
.HasName("pk_lodestone_auth");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_lodestone_auth_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("lodestone_auth", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("uid");
|
||||||
|
|
||||||
|
b.Property<string>("CharacterIdentification")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("character_identification");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAdmin")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_admin");
|
||||||
|
|
||||||
|
b.Property<bool>("IsModerator")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_moderator");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastLoggedIn")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last_logged_in");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Timestamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("timestamp");
|
||||||
|
|
||||||
|
b.HasKey("UID")
|
||||||
|
.HasName("pk_users");
|
||||||
|
|
||||||
|
b.HasIndex("CharacterIdentification")
|
||||||
|
.HasDatabaseName("ix_users_character_identification");
|
||||||
|
|
||||||
|
b.ToTable("users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.Auth", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.HasConstraintName("fk_auth_users_user_temp_id");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.ClientPair", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "OtherUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OtherUserUID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_client_pairs_users_other_user_temp_id1");
|
||||||
|
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_client_pairs_users_user_temp_id2");
|
||||||
|
|
||||||
|
b.Navigation("OtherUser");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.FileCache", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "Uploader")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UploaderUID")
|
||||||
|
.HasConstraintName("fk_file_caches_users_uploader_uid");
|
||||||
|
|
||||||
|
b.Navigation("Uploader");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.HasConstraintName("fk_lodestone_auth_users_user_uid");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddNullableLodestoneAuthProperties : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<DateTime>(
|
||||||
|
name: "started_at",
|
||||||
|
table: "lodestone_auth",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(DateTime),
|
||||||
|
oldType: "timestamp with time zone");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<DateTime>(
|
||||||
|
name: "started_at",
|
||||||
|
table: "lodestone_auth",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||||
|
oldClrType: typeof(DateTime),
|
||||||
|
oldType: "timestamp with time zone",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,6 +155,38 @@ namespace MareSynchronosServer.Migrations
|
|||||||
b.ToTable("forbidden_upload_entries", (string)null);
|
b.ToTable("forbidden_upload_entries", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("DiscordId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("discord_id");
|
||||||
|
|
||||||
|
b.Property<string>("HashedLodestoneId")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("hashed_lodestone_id");
|
||||||
|
|
||||||
|
b.Property<string>("LodestoneAuthString")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("lodestone_auth_string");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("started_at");
|
||||||
|
|
||||||
|
b.Property<string>("UserUID")
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("user_uid");
|
||||||
|
|
||||||
|
b.HasKey("DiscordId")
|
||||||
|
.HasName("pk_lodestone_auth");
|
||||||
|
|
||||||
|
b.HasIndex("UserUID")
|
||||||
|
.HasDatabaseName("ix_lodestone_auth_user_uid");
|
||||||
|
|
||||||
|
b.ToTable("lodestone_auth", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MareSynchronosServer.Models.User", b =>
|
modelBuilder.Entity("MareSynchronosServer.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("UID")
|
b.Property<string>("UID")
|
||||||
@@ -233,6 +265,16 @@ namespace MareSynchronosServer.Migrations
|
|||||||
|
|
||||||
b.Navigation("Uploader");
|
b.Navigation("Uploader");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosServer.Models.LodeStoneAuth", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosServer.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserUID")
|
||||||
|
.HasConstraintName("fk_lodestone_auth_users_user_uid");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Models
|
||||||
|
{
|
||||||
|
public class LodeStoneAuth
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public ulong DiscordId { get; set; }
|
||||||
|
public string HashedLodestoneId { get; set; }
|
||||||
|
public string? LodestoneAuthString { get; set; }
|
||||||
|
public User? User { get; set; }
|
||||||
|
public DateTime? StartedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ namespace MareSynchronosServer
|
|||||||
user.CharacterIdentification = null;
|
user.CharacterIdentification = null;
|
||||||
}
|
}
|
||||||
var looseFiles = context.Files.Where(f => f.Uploaded == false);
|
var looseFiles = context.Files.Where(f => f.Uploaded == false);
|
||||||
|
var unfinishedRegistrations = context.LodeStoneAuth.Where(c => c.StartedAt != null);
|
||||||
|
context.RemoveRange(unfinishedRegistrations);
|
||||||
context.RemoveRange(looseFiles);
|
context.RemoveRange(looseFiles);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ using Prometheus;
|
|||||||
using WebSocketOptions = Microsoft.AspNetCore.Builder.WebSocketOptions;
|
using WebSocketOptions = Microsoft.AspNetCore.Builder.WebSocketOptions;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using MareSynchronosServer.Discord;
|
||||||
|
using AspNetCoreRateLimit;
|
||||||
|
|
||||||
namespace MareSynchronosServer
|
namespace MareSynchronosServer
|
||||||
{
|
{
|
||||||
@@ -41,6 +43,13 @@ namespace MareSynchronosServer
|
|||||||
hubOptions.StreamBufferCapacity = 200;
|
hubOptions.StreamBufferCapacity = 200;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddMemoryCache();
|
||||||
|
|
||||||
|
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
|
||||||
|
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
|
||||||
|
|
||||||
|
services.AddInMemoryRateLimiting();
|
||||||
|
|
||||||
services.AddSingleton<SystemInfoService, SystemInfoService>();
|
services.AddSingleton<SystemInfoService, SystemInfoService>();
|
||||||
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||||
services.AddTransient(_ => Configuration);
|
services.AddTransient(_ => Configuration);
|
||||||
@@ -55,6 +64,7 @@ namespace MareSynchronosServer
|
|||||||
|
|
||||||
services.AddHostedService<FileCleanupService>();
|
services.AddHostedService<FileCleanupService>();
|
||||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||||
|
services.AddHostedService<DiscordBot>();
|
||||||
|
|
||||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||||
services.AddAuthentication(options =>
|
services.AddAuthentication(options =>
|
||||||
@@ -63,6 +73,8 @@ namespace MareSynchronosServer
|
|||||||
})
|
})
|
||||||
.AddScheme<AuthenticationSchemeOptions, SecretKeyAuthenticationHandler>(SecretKeyAuthenticationHandler.AuthScheme, options => { });
|
.AddScheme<AuthenticationSchemeOptions, SecretKeyAuthenticationHandler>(SecretKeyAuthenticationHandler.AuthScheme, options => { });
|
||||||
services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
|
services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
|
||||||
|
|
||||||
|
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
@@ -81,6 +93,8 @@ namespace MareSynchronosServer
|
|||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseHttpLogging();
|
app.UseHttpLogging();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DiscordServerUrl": "",
|
||||||
|
"DiscordBotToken": "",
|
||||||
"UnusedFileRetentionPeriodInDays": 7,
|
"UnusedFileRetentionPeriodInDays": 7,
|
||||||
"PurgeUnusedAccounts": true,
|
"PurgeUnusedAccounts": true,
|
||||||
"PurgeUnusedAccountsPeriodInDays": 14,
|
"PurgeUnusedAccountsPeriodInDays": 14,
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
"Certificate": {
|
"Certificate": {
|
||||||
"Subject": "darkarchon.internet-box.ch",
|
"Subject": "darkarchon.internet-box.ch",
|
||||||
"Store": "My",
|
"Store": "My",
|
||||||
"Location": "LocalMachine",
|
"Location": "LocalMachine"
|
||||||
//"AllowInvalid": false
|
//"AllowInvalid": false
|
||||||
// "Path": "", //use path, keypath and password to provide a valid certificate if not using windows key store
|
// "Path": "", //use path, keypath and password to provide a valid certificate if not using windows key store
|
||||||
// "KeyPath": ""
|
// "KeyPath": ""
|
||||||
@@ -43,5 +45,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {
|
||||||
|
"EnableEndpointRateLimiting": false,
|
||||||
|
"StackBlockedRequests": false,
|
||||||
|
"RealIpHeader": "X-Real-IP",
|
||||||
|
"ClientIdHeader": "X-ClientId",
|
||||||
|
"HttpStatusCode": 429,
|
||||||
|
"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ],
|
||||||
|
"GeneralRules": [
|
||||||
|
{
|
||||||
|
"Endpoint": "*",
|
||||||
|
"Period": "1s",
|
||||||
|
"Limit": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IPRateLimitPolicies": {
|
||||||
|
"IpRules": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user