Compare commits

...

6 Commits

Author SHA1 Message Date
cake
dd4cb73b9b Change lightfinder permissions for groups 2025-11-18 00:27:25 +01:00
cake
ab9cdeb682 Upped hashing 2025-11-17 19:37:23 +01:00
63211b2e8b Merge pull request 'Fix Fk' (#34) from fkfix into master
Reviewed-on: #34
2025-11-17 18:34:18 +01:00
defnotken
a1280d58bf Fix Fk 2025-11-17 09:34:33 -06:00
34f0223a85 revert revert regex 2025-11-13 15:50:19 +01:00
69f06f5868 Merge pull request 'revert regex' (#33) from revert-regex into master
Reviewed-on: #33
2025-11-13 15:22:20 +01:00
10 changed files with 1407 additions and 27 deletions

View File

@@ -995,11 +995,11 @@ public partial class LightlessHub
return false;
}
var (isOwner, _) = await TryValidateOwner(dto.GID).ConfigureAwait(false);
var (isOwner, _) = await TryValidateGroupModeratorOrOwner(dto.GID).ConfigureAwait(false);
if (!isOwner)
{
_logger.LogCallWarning(LightlessHubLogger.Args("Unauthorized syncshell broadcast change", "User", UserUID, "GID", dto.GID));
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You must be the owner of the syncshell to broadcast it.");
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You must be the owner or moderator of the syncshell to broadcast it.");
return false;
}

View File

@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using StackExchange.Redis;
using System.Text;
using System.Text.Json;

View File

@@ -21,6 +21,7 @@
<ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Blake3" Version="2.0.0" />
<PackageReference Include="IDisposableAnalyzers" Version="4.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -329,13 +329,11 @@ public partial class LightlessWizardModule : InteractionModuleBase
private int? ParseCharacterIdFromLodestoneUrl(string lodestoneUrl)
{
var regex = new Regex(@"https:\/\/(na|eu|de|fr|jp)\.finalfantasyxiv\.com\/lodestone\/character\/\d+");
var regex = new Regex(@"^https:\/\/(na|eu|de|fr|jp)\.finalfantasyxiv\.com\/lodestone\/character\/(\d+)/?$");
var matches = regex.Match(lodestoneUrl);
var isLodestoneUrl = matches.Success;
if (!isLodestoneUrl || matches.Groups.Count < 1) return null;
lodestoneUrl = matches.Groups[0].ToString();
var stringId = lodestoneUrl.Split('/', StringSplitOptions.RemoveEmptyEntries).Last();
var stringId = matches.Groups[2].ToString();
if (!int.TryParse(stringId, out int lodestoneId))
{

View File

@@ -91,6 +91,12 @@ public class LightlessDbContext : DbContext
mb.Entity<GroupProfile>().ToTable("group_profiles");
mb.Entity<GroupProfile>().HasKey(u => u.GroupGID);
mb.Entity<GroupProfile>().HasIndex(c => c.GroupGID);
mb.Entity<Group>()
.HasOne(g => g.Profile)
.WithOne(p => p.Group)
.HasForeignKey<GroupProfile>(p => p.GroupGID)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
mb.Entity<GroupTempInvite>().ToTable("group_temp_invites");
mb.Entity<GroupTempInvite>().HasKey(u => new { u.GroupGID, u.Invite });
mb.Entity<GroupTempInvite>().HasIndex(c => c.GroupGID);

View File

@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LightlessSyncServer.Migrations
{
/// <inheritdoc />
public partial class FixForeignKeyGroupProfiles : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_group_profiles_groups_group_gid",
table: "group_profiles");
migrationBuilder.AddForeignKey(
name: "fk_group_profiles_groups_group_gid",
table: "group_profiles",
column: "group_gid",
principalTable: "groups",
principalColumn: "gid",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_group_profiles_groups_group_gid",
table: "group_profiles");
migrationBuilder.AddForeignKey(
name: "fk_group_profiles_groups_group_gid",
table: "group_profiles",
column: "group_gid",
principalTable: "groups",
principalColumn: "gid");
}
}
}

View File

@@ -820,14 +820,14 @@ namespace LightlessSyncServer.Migrations
.HasColumnType("character varying(15)")
.HasColumnName("alias");
b.Property<bool>("HasVanity")
.HasColumnType("boolean")
.HasColumnName("has_vanity");
b.Property<bool>("ChatBanned")
.HasColumnType("boolean")
.HasColumnName("chat_banned");
b.Property<bool>("HasVanity")
.HasColumnType("boolean")
.HasColumnName("has_vanity");
b.Property<bool>("IsAdmin")
.HasColumnType("boolean")
.HasColumnName("is_admin");
@@ -1220,6 +1220,7 @@ namespace LightlessSyncServer.Migrations
b.HasOne("LightlessSyncShared.Models.Group", "Group")
.WithOne("Profile")
.HasForeignKey("LightlessSyncShared.Models.GroupProfile", "GroupGID")
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("fk_group_profiles_groups_group_gid");
b.Navigation("Group");

View File

@@ -1,4 +1,5 @@
using K4os.Compression.LZ4.Legacy;
using Blake3;
using K4os.Compression.LZ4.Legacy;
using LightlessSync.API.Dto.Files;
using LightlessSync.API.Routes;
using LightlessSync.API.SignalR;
@@ -208,11 +209,14 @@ public class ServerFilesController : ControllerBase
[RequestSizeLimit(200 * 1024 * 1024)]
public async Task<IActionResult> UploadFile(string hash, CancellationToken requestAborted)
{
using var dbContext = await _lightlessDbContext.CreateDbContextAsync();
await using var dbContext = await _lightlessDbContext.CreateDbContextAsync();
_logger.LogInformation("{user}|{file}: Uploading", LightlessUser, hash);
if (hash.Length == 40)
{
hash = hash.ToUpperInvariant();
}
hash = hash.ToUpperInvariant();
var existingFile = await dbContext.Files.SingleOrDefaultAsync(f => f.Hash == hash);
if (existingFile != null) return Ok();
@@ -263,10 +267,14 @@ public class ServerFilesController : ControllerBase
[RequestSizeLimit(200 * 1024 * 1024)]
public async Task<IActionResult> UploadFileMunged(string hash, CancellationToken requestAborted)
{
using var dbContext = await _lightlessDbContext.CreateDbContextAsync();
await using var dbContext = await _lightlessDbContext.CreateDbContextAsync();
_logger.LogInformation("{user}|{file}: Uploading munged", LightlessUser, hash);
hash = hash.ToUpperInvariant();
if (hash.Length == 40)
{
hash = hash.ToUpperInvariant();
}
var existingFile = await dbContext.Files.SingleOrDefaultAsync(f => f.Hash == hash);
if (existingFile != null) return Ok();
@@ -319,20 +327,26 @@ public class ServerFilesController : ControllerBase
private async Task StoreData(string hash, LightlessDbContext dbContext, MemoryStream compressedFileStream)
{
var decompressedData = LZ4Wrapper.Unwrap(compressedFileStream.ToArray());
// reset streams
compressedFileStream.Seek(0, SeekOrigin.Begin);
// compute hash to verify
var hashString = BitConverter.ToString(SHA1.HashData(decompressedData))
.Replace("-", "", StringComparison.Ordinal).ToUpperInvariant();
if (!string.Equals(hashString, hash, StringComparison.Ordinal))
throw new InvalidOperationException($"{LightlessUser}|{hash}: Hash does not match file, computed: {hashString}, expected: {hash}");
bool valid;
// save file
var path = FilePathUtil.GetFilePath(_basePath, hash);
using var fileStream = new FileStream(path, FileMode.Create);
await compressedFileStream.CopyToAsync(fileStream).ConfigureAwait(false);
_logger.LogDebug("{user}|{file}: Uploaded file saved to {path}", LightlessUser, hash, path);
if (hash.Length == 40)
{
var sha1Hex = Convert.ToHexString(SHA1.HashData(decompressedData));
valid = string.Equals(sha1Hex, hash, StringComparison.OrdinalIgnoreCase);
}
else
{
var blakeHash = Hasher.Hash(decompressedData);
var blakeHex = Convert.ToHexString(blakeHash.AsSpan());
valid = string.Equals(blakeHex, hash, StringComparison.OrdinalIgnoreCase);
}
if (!valid)
throw new InvalidOperationException(
$"{LightlessUser}|{hash}: Hash does not match file, computed mismatch."
);
// update on db
await dbContext.Files.AddAsync(new FileCache()

View File

@@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blake3" Version="2.0.0" />
<PackageReference Include="IDisposableAnalyzers" Version="4.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>