diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs
index 444aa0f..652fed9 100644
--- a/VoidCat/Controllers/UploadController.cs
+++ b/VoidCat/Controllers/UploadController.cs
@@ -214,12 +214,12 @@ namespace VoidCat.Controllers
if (req.Strike != default)
{
- await _paywall.Set(gid, req.Strike!);
+ await _paywall.Add(gid, req.Strike!);
return Ok();
}
// if none set, set NoPaywallConfig
- await _paywall.Set(gid, new NoPaywallConfig());
+ await _paywall.Add(gid, new NoPaywallConfig());
return Ok();
}
diff --git a/VoidCat/Model/VirusScanResult.cs b/VoidCat/Model/VirusScanResult.cs
index f609c23..aab6c0c 100644
--- a/VoidCat/Model/VirusScanResult.cs
+++ b/VoidCat/Model/VirusScanResult.cs
@@ -1,10 +1,46 @@
-namespace VoidCat.Model;
+using Newtonsoft.Json;
+namespace VoidCat.Model;
+
+///
+/// Results for virus scan of a single file
+///
public sealed class VirusScanResult
{
- public DateTimeOffset ScanTime { get; init; } = DateTimeOffset.UtcNow;
-
- public bool IsVirus { get; init; }
+ ///
+ /// Unique Id for this scan
+ ///
+ [JsonConverter(typeof(Base58GuidConverter))]
+ public Guid Id { get; init; }
- public List VirusNames { get; init; } = new();
+ ///
+ /// Id of the file that was scanned
+ ///
+ [JsonConverter(typeof(Base58GuidConverter))]
+ public Guid File { get; init; }
+
+ ///
+ /// Time the file was scanned
+ ///
+ public DateTimeOffset ScanTime { get; init; } = DateTimeOffset.UtcNow;
+
+ ///
+ /// The name of the virus scanner software
+ ///
+ public string Scanner { get; init; } = null!;
+
+ ///
+ /// Virus detection score, this can mean different things for each scanner but the value should be between 0 and 1
+ ///
+ public decimal Score { get; init; }
+
+ ///
+ /// Detected virus names
+ ///
+ public string? Names { get; init; }
+
+ ///
+ /// If we consider this result as a virus or not
+ ///
+ public bool IsVirus => Score >= 0.75m && !string.IsNullOrEmpty(Names);
}
\ No newline at end of file
diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs
index a6b68bf..74bb0b5 100644
--- a/VoidCat/Program.cs
+++ b/VoidCat/Program.cs
@@ -165,7 +165,7 @@ if (!string.IsNullOrEmpty(voidSettings.Postgres))
services.AddTransient();
services.AddFluentMigratorCore()
.ConfigureRunner(r =>
- r.AddPostgres11_0()
+ r.AddPostgres()
.WithGlobalConnectionString(voidSettings.Postgres)
.ScanIn(typeof(Program).Assembly).For.Migrations());
}
diff --git a/VoidCat/Services/Abstractions/IBasicStore.cs b/VoidCat/Services/Abstractions/IBasicStore.cs
index 8fa1f20..169ce69 100644
--- a/VoidCat/Services/Abstractions/IBasicStore.cs
+++ b/VoidCat/Services/Abstractions/IBasicStore.cs
@@ -1,14 +1,37 @@
namespace VoidCat.Services.Abstractions;
+///
+/// Simple CRUD interface for data stores
+///
+///
public interface IBasicStore
{
+ ///
+ /// Get a single item from the store
+ ///
+ ///
+ ///
ValueTask Get(Guid id);
+ ///
+ /// Get multiple items from the store
+ ///
+ ///
+ ///
ValueTask> Get(Guid[] ids);
- ValueTask Set(Guid id, T obj);
+ ///
+ /// Add an item to the store
+ ///
+ ///
+ ///
+ ///
+ ValueTask Add(Guid id, T obj);
+ ///
+ /// Delete an item from the store
+ ///
+ ///
+ ///
ValueTask Delete(Guid id);
-
- string MapKey(Guid id);
}
\ No newline at end of file
diff --git a/VoidCat/Services/Abstractions/ICache.cs b/VoidCat/Services/Abstractions/ICache.cs
index 3ebbd40..8af057d 100644
--- a/VoidCat/Services/Abstractions/ICache.cs
+++ b/VoidCat/Services/Abstractions/ICache.cs
@@ -1,13 +1,56 @@
namespace VoidCat.Services.Abstractions;
+///
+/// Basic KV cache interface
+///
public interface ICache
{
+ ///
+ /// Get a single object from cache by its key
+ ///
+ ///
+ ///
+ ///
ValueTask Get(string key);
+
+ ///
+ /// Set the the value of a key in the cache
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
ValueTask Set(string key, T value, TimeSpan? expire = null);
+
+ ///
+ /// Delete an object from the cache
+ ///
+ ///
+ ///
ValueTask Delete(string key);
+ ///
+ /// Return a list of items at the specified key
+ ///
+ ///
+ ///
ValueTask GetList(string key);
+
+ ///
+ /// Add an item to the list at the specified key
+ ///
+ ///
+ ///
+ ///
ValueTask AddToList(string key, string value);
+
+ ///
+ /// Remove an item from the list at a the specified key
+ ///
+ ///
+ ///
+ ///
ValueTask RemoveFromList(string key, string value);
}
diff --git a/VoidCat/Services/Abstractions/IVirusScanStore.cs b/VoidCat/Services/Abstractions/IVirusScanStore.cs
index f08cdae..bacd56f 100644
--- a/VoidCat/Services/Abstractions/IVirusScanStore.cs
+++ b/VoidCat/Services/Abstractions/IVirusScanStore.cs
@@ -2,6 +2,15 @@
namespace VoidCat.Services.Abstractions;
+///
+/// Store for virus scan results
+///
public interface IVirusScanStore : IBasicStore
{
+ ///
+ /// Get the latest scan result by file id
+ ///
+ ///
+ ///
+ ValueTask GetByFile(Guid id);
}
\ No newline at end of file
diff --git a/VoidCat/Services/Abstractions/IVirusScanner.cs b/VoidCat/Services/Abstractions/IVirusScanner.cs
index 2d4ca05..fea46b3 100644
--- a/VoidCat/Services/Abstractions/IVirusScanner.cs
+++ b/VoidCat/Services/Abstractions/IVirusScanner.cs
@@ -2,7 +2,16 @@
namespace VoidCat.Services.Abstractions;
+///
+/// File virus scanning interface
+///
public interface IVirusScanner
{
+ ///
+ /// Scan a single file
+ ///
+ ///
+ ///
+ ///
ValueTask ScanFile(Guid id, CancellationToken cts);
}
\ No newline at end of file
diff --git a/VoidCat/Services/Background/VirusScannerService.cs b/VoidCat/Services/Background/VirusScannerService.cs
index 379f4c8..f0fd93a 100644
--- a/VoidCat/Services/Background/VirusScannerService.cs
+++ b/VoidCat/Services/Background/VirusScannerService.cs
@@ -36,13 +36,15 @@ public class VirusScannerService : BackgroundService
await foreach (var file in files.Results.WithCancellation(stoppingToken))
{
// check for scans
- var scan = await _scanStore.Get(file.Id);
+ var scan = await _scanStore.GetByFile(file.Id);
if (scan == default)
{
try
{
var result = await _scanner.ScanFile(file.Id, stoppingToken);
- await _scanStore.Set(file.Id, result);
+ await _scanStore.Add(result.Id, result);
+ _logger.LogInformation("Scanned file {Id}, IsVirus = {Result}", result.File,
+ result.IsVirus);
}
catch (RateLimitedException rx)
{
diff --git a/VoidCat/Services/BasicCacheStore.cs b/VoidCat/Services/BasicCacheStore.cs
index c0ef2aa..4e02ab6 100644
--- a/VoidCat/Services/BasicCacheStore.cs
+++ b/VoidCat/Services/BasicCacheStore.cs
@@ -2,26 +2,29 @@
namespace VoidCat.Services;
+///
public abstract class BasicCacheStore : IBasicStore
{
- protected readonly ICache _cache;
+ protected readonly ICache Cache;
protected BasicCacheStore(ICache cache)
{
- _cache = cache;
+ Cache = cache;
}
+ ///
public virtual ValueTask Get(Guid id)
{
- return _cache.Get(MapKey(id));
+ return Cache.Get(MapKey(id));
}
+ ///
public virtual async ValueTask> Get(Guid[] ids)
{
var ret = new List();
foreach (var id in ids)
{
- var r = await _cache.Get(MapKey(id));
+ var r = await Cache.Get(MapKey(id));
if (r != null)
{
ret.Add(r);
@@ -31,15 +34,22 @@ public abstract class BasicCacheStore : IBasicStore
return ret;
}
- public virtual ValueTask Set(Guid id, TStore obj)
+ ///
+ public virtual ValueTask Add(Guid id, TStore obj)
{
- return _cache.Set(MapKey(id), obj);
+ return Cache.Set(MapKey(id), obj);
}
+ ///
public virtual ValueTask Delete(Guid id)
{
- return _cache.Delete(MapKey(id));
+ return Cache.Delete(MapKey(id));
}
- public abstract string MapKey(Guid id);
+ ///
+ /// Map an id to a key in the KV store
+ ///
+ ///
+ ///
+ protected abstract string MapKey(Guid id);
}
\ No newline at end of file
diff --git a/VoidCat/Services/Files/FileInfoManager.cs b/VoidCat/Services/Files/FileInfoManager.cs
index 767685d..a25d159 100644
--- a/VoidCat/Services/Files/FileInfoManager.cs
+++ b/VoidCat/Services/Files/FileInfoManager.cs
@@ -67,7 +67,7 @@ public class FileInfoManager : IFileInfoManager
var meta = _metadataStore.Get(id);
var paywall = _paywallStore.Get(id);
var bandwidth = _statsReporter.GetBandwidth(id);
- var virusScan = _virusScanStore.Get(id);
+ var virusScan = _virusScanStore.GetByFile(id);
var uploader = _userUploadsStore.Uploader(id);
await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask(), virusScan.AsTask(), uploader.AsTask());
diff --git a/VoidCat/Services/Migrations/Database/00-Init.cs b/VoidCat/Services/Migrations/Database/00-Init.cs
index e5f2cb5..051e506 100644
--- a/VoidCat/Services/Migrations/Database/00-Init.cs
+++ b/VoidCat/Services/Migrations/Database/00-Init.cs
@@ -1,4 +1,5 @@
-using FluentMigrator;
+using System.Data;
+using FluentMigrator;
using VoidCat.Model;
namespace VoidCat.Services.Migrations.Database;
@@ -12,8 +13,8 @@ public class Init : Migration
.WithColumn("Id").AsGuid().PrimaryKey()
.WithColumn("Email").AsString().NotNullable().Indexed()
.WithColumn("Password").AsString()
- .WithColumn("Created").AsDateTime().WithDefault(SystemMethods.CurrentDateTime)
- .WithColumn("LastLogin").AsDateTime().Nullable()
+ .WithColumn("Created").AsDateTimeOffset().WithDefault(SystemMethods.CurrentUTCDateTime)
+ .WithColumn("LastLogin").AsDateTimeOffset().Nullable()
.WithColumn("Avatar").AsString().Nullable()
.WithColumn("DisplayName").AsString().WithDefaultValue("void user")
.WithColumn("Flags").AsInt32().WithDefaultValue((int) VoidUserFlags.PublicProfile);
@@ -22,29 +23,54 @@ public class Init : Migration
.WithColumn("Id").AsGuid().PrimaryKey()
.WithColumn("Name").AsString()
.WithColumn("Size").AsInt64()
- .WithColumn("Uploaded").AsDateTime().Indexed().WithDefault(SystemMethods.CurrentDateTime)
+ .WithColumn("Uploaded").AsDateTimeOffset().Indexed().WithDefault(SystemMethods.CurrentUTCDateTime)
.WithColumn("Description").AsString().Nullable()
.WithColumn("MimeType").AsString().WithDefaultValue("application/octet-stream")
.WithColumn("Digest").AsString()
.WithColumn("EditSecret").AsGuid();
Create.Table("UserFiles")
- .WithColumn("File").AsGuid().ForeignKey("Files", "Id")
- .WithColumn("User").AsGuid().ForeignKey("Users", "Id").Indexed();
+ .WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).Indexed()
+ .WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed();
Create.UniqueConstraint()
.OnTable("UserFiles")
.Columns("File", "User");
Create.Table("Paywall")
- .WithColumn("File").AsGuid().ForeignKey("Files", "Id").Unique()
+ .WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).PrimaryKey()
.WithColumn("Type").AsInt16()
.WithColumn("Currency").AsInt16()
.WithColumn("Amount").AsDecimal();
Create.Table("PaywallStrike")
- .WithColumn("File").AsGuid().ForeignKey("Files", "Id").Unique()
+ .WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).PrimaryKey()
.WithColumn("Handle").AsString();
+
+ Create.Table("UserRoles")
+ .WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed()
+ .WithColumn("Role").AsString().NotNullable();
+
+ Create.UniqueConstraint()
+ .OnTable("UserRoles")
+ .Columns("User", "Role");
+
+ Create.Table("EmailVerification")
+ .WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade)
+ .WithColumn("Code").AsGuid()
+ .WithColumn("Expires").AsDateTimeOffset();
+
+ Create.UniqueConstraint()
+ .OnTable("EmailVerification")
+ .Columns("User", "Code");
+
+ Create.Table("VirusScanResult")
+ .WithColumn("Id").AsGuid().PrimaryKey()
+ .WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).Indexed()
+ .WithColumn("ScanTime").AsDateTimeOffset().WithDefault(SystemMethods.CurrentUTCDateTime)
+ .WithColumn("Scanner").AsString()
+ .WithColumn("Score").AsDecimal()
+ .WithColumn("Names").AsString().Nullable();
}
public override void Down()
@@ -54,5 +80,8 @@ public class Init : Migration
Delete.Table("UsersFiles");
Delete.Table("Paywall");
Delete.Table("PaywallStrike");
+ Delete.Table("UserRoles");
+ Delete.Table("EmailVerification");
+ Delete.Table("VirusScanResult");
}
}
\ No newline at end of file
diff --git a/VoidCat/Services/Migrations/Database/01-Roles.cs b/VoidCat/Services/Migrations/Database/01-Roles.cs
deleted file mode 100644
index 1c71484..0000000
--- a/VoidCat/Services/Migrations/Database/01-Roles.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using FluentMigrator;
-
-namespace VoidCat.Services.Migrations.Database;
-
-[Migration(20220608_1345)]
-public class UserRoles : Migration
-{
- public override void Up()
- {
- Create.Table("UserRoles")
- .WithColumn("User").AsGuid().ForeignKey("Users", "Id").PrimaryKey()
- .WithColumn("Role").AsString().NotNullable();
-
- Create.UniqueConstraint()
- .OnTable("UserRoles")
- .Columns("User", "Role");
- }
-
- public override void Down()
- {
- Delete.Table("UserRoles");
- }
-}
\ No newline at end of file
diff --git a/VoidCat/Services/Migrations/Database/02-EmailVerification.cs b/VoidCat/Services/Migrations/Database/02-EmailVerification.cs
deleted file mode 100644
index 0672ac2..0000000
--- a/VoidCat/Services/Migrations/Database/02-EmailVerification.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using FluentMigrator;
-
-namespace VoidCat.Services.Migrations.Database;
-
-[Migration(20220608_1443)]
-public class EmailVerification : Migration
-{
- public override void Up()
- {
- Create.Table("EmailVerification")
- .WithColumn("User").AsGuid().ForeignKey("Users", "Id")
- .WithColumn("Code").AsGuid()
- .WithColumn("Expires").AsDateTime();
-
- Create.UniqueConstraint()
- .OnTable("EmailVerification")
- .Columns("User", "Code");
- }
-
- public override void Down()
- {
- Delete.Table("EmailVerification");
- }
-}
\ No newline at end of file
diff --git a/VoidCat/Services/Paywall/PaywallStore.cs b/VoidCat/Services/Paywall/PaywallStore.cs
index 59dff70..8dcd43a 100644
--- a/VoidCat/Services/Paywall/PaywallStore.cs
+++ b/VoidCat/Services/Paywall/PaywallStore.cs
@@ -10,28 +10,29 @@ public class PaywallStore : BasicCacheStore, IPaywallStore
{
}
+ ///
public override async ValueTask Get(Guid id)
{
- var cfg = await _cache.Get(MapKey(id));
+ var cfg = await Cache.Get(MapKey(id));
return cfg?.Service switch
{
PaywallServices.None => cfg,
- PaywallServices.Strike => await _cache.Get(MapKey(id)),
+ PaywallServices.Strike => await Cache.Get(MapKey(id)),
_ => default
};
}
public async ValueTask GetOrder(Guid id)
{
- return await _cache.Get(OrderKey(id));
+ return await Cache.Get(OrderKey(id));
}
public ValueTask SaveOrder(PaywallOrder order)
{
- return _cache.Set(OrderKey(order.Id), order,
+ return Cache.Set(OrderKey(order.Id), order,
order.Status == PaywallOrderStatus.Paid ? TimeSpan.FromDays(1) : TimeSpan.FromSeconds(5));
}
- public override string MapKey(Guid id) => $"paywall:config:{id}";
+ protected override string MapKey(Guid id) => $"paywall:config:{id}";
private string OrderKey(Guid id) => $"paywall:order:{id}";
}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/CacheVirusScanStore.cs b/VoidCat/Services/VirusScanner/CacheVirusScanStore.cs
new file mode 100644
index 0000000..cc25a59
--- /dev/null
+++ b/VoidCat/Services/VirusScanner/CacheVirusScanStore.cs
@@ -0,0 +1,38 @@
+using VoidCat.Model;
+using VoidCat.Services.Abstractions;
+
+namespace VoidCat.Services.VirusScanner;
+
+///
+public class CacheVirusScanStore : BasicCacheStore, IVirusScanStore
+{
+ public CacheVirusScanStore(ICache cache) : base(cache)
+ {
+ }
+
+ ///
+ public override async ValueTask Add(Guid id, VirusScanResult obj)
+ {
+ await base.Add(id, obj);
+ await Cache.AddToList(MapFilesKey(id), obj.Id.ToString());
+ }
+
+ ///
+ public async ValueTask GetByFile(Guid id)
+ {
+ var scans = await Cache.GetList(MapFilesKey(id));
+ if (scans.Length > 0)
+ {
+ return await Get(Guid.Parse(scans.First()));
+ }
+
+ return default;
+ }
+
+ ///
+ protected override string MapKey(Guid id)
+ => $"virus-scan:{id}";
+
+ private string MapFilesKey(Guid id)
+ => $"virus-scan:file:{id}";
+}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/ClamAvScanner.cs b/VoidCat/Services/VirusScanner/ClamAvScanner.cs
index 7b78bbb..79e2f43 100644
--- a/VoidCat/Services/VirusScanner/ClamAvScanner.cs
+++ b/VoidCat/Services/VirusScanner/ClamAvScanner.cs
@@ -4,6 +4,9 @@ using VoidCat.Services.Abstractions;
namespace VoidCat.Services.VirusScanner;
+///
+/// ClamAV scanner
+///
public class ClamAvScanner : IVirusScanner
{
private readonly ILogger _logger;
@@ -17,6 +20,7 @@ public class ClamAvScanner : IVirusScanner
_store = store;
}
+ ///
public async ValueTask ScanFile(Guid id, CancellationToken cts)
{
_logger.LogInformation("Starting scan of {Filename}", id);
@@ -31,8 +35,11 @@ public class ClamAvScanner : IVirusScanner
return new()
{
- IsVirus = result.Result == ClamScanResults.VirusDetected,
- VirusNames = result.InfectedFiles?.Select(a => a.VirusName.Trim()).ToList() ?? new()
+ Id = Guid.NewGuid(),
+ File = id,
+ Score = result.Result == ClamScanResults.VirusDetected ? 1m : 0m,
+ Names = string.Join(",", result.InfectedFiles?.Select(a => a.VirusName.Trim()) ?? Array.Empty()),
+ Scanner = "ClamAV"
};
}
}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/PostgresVirusScanStore.cs b/VoidCat/Services/VirusScanner/PostgresVirusScanStore.cs
new file mode 100644
index 0000000..2c0b7b9
--- /dev/null
+++ b/VoidCat/Services/VirusScanner/PostgresVirusScanStore.cs
@@ -0,0 +1,63 @@
+using Dapper;
+using VoidCat.Model;
+using VoidCat.Services.Abstractions;
+
+namespace VoidCat.Services.VirusScanner;
+
+///
+public class PostgresVirusScanStore : IVirusScanStore
+{
+ private readonly PostgresConnectionFactory _connection;
+
+ public PostgresVirusScanStore(PostgresConnectionFactory connection)
+ {
+ _connection = connection;
+ }
+
+ ///
+ public async ValueTask Get(Guid id)
+ {
+ await using var conn = await _connection.Get();
+ return await conn.QuerySingleOrDefaultAsync(
+ @"select * from ""VirusScanResult"" where ""Id"" = :id", new {id});
+ }
+
+ ///
+ public async ValueTask GetByFile(Guid id)
+ {
+ await using var conn = await _connection.Get();
+ return await conn.QuerySingleOrDefaultAsync(
+ @"select * from ""VirusScanResult"" where ""File"" = :file", new {file = id});
+ }
+
+ ///
+ public async ValueTask> Get(Guid[] ids)
+ {
+ await using var conn = await _connection.Get();
+ return (await conn.QueryAsync(
+ @"select * from ""VirusScanResult"" where ""Id"" in :ids", new {ids = ids.ToArray()})).ToList();
+ }
+
+ ///
+ public async ValueTask Add(Guid id, VirusScanResult obj)
+ {
+ await using var conn = await _connection.Get();
+ await conn.ExecuteAsync(
+ @"insert into ""VirusScanResult""(""Id"", ""File"", ""Scanner"", ""Score"", ""Names"") values(:id, :file, :scanner, :score, :names)",
+ new
+ {
+ id,
+ file = obj.File,
+ scanner = obj.Scanner,
+ score = obj.Score,
+ names = obj.Names
+ });
+ }
+
+ ///
+ public async ValueTask Delete(Guid id)
+ {
+ await using var conn = await _connection.Get();
+ await conn.ExecuteAsync(@"delete from ""VirusScanResult"" where ""Id"" = :id", new {id});
+ }
+}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/VirusScanStore.cs b/VoidCat/Services/VirusScanner/VirusScanStore.cs
deleted file mode 100644
index f230bd6..0000000
--- a/VoidCat/Services/VirusScanner/VirusScanStore.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using VoidCat.Model;
-using VoidCat.Services.Abstractions;
-
-namespace VoidCat.Services.VirusScanner;
-
-public class VirusScanStore : BasicCacheStore, IVirusScanStore
-{
- public VirusScanStore(ICache cache) : base(cache)
- {
- }
-
- public override string MapKey(Guid id)
- => $"virus-scan:{id}";
-}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/VirusScannerStartup.cs b/VoidCat/Services/VirusScanner/VirusScannerStartup.cs
index a715569..0df1f11 100644
--- a/VoidCat/Services/VirusScanner/VirusScannerStartup.cs
+++ b/VoidCat/Services/VirusScanner/VirusScannerStartup.cs
@@ -1,7 +1,6 @@
using nClam;
using VoidCat.Model;
using VoidCat.Services.Abstractions;
-using VoidCat.Services.VirusScanner.VirusTotal;
namespace VoidCat.Services.VirusScanner;
@@ -9,7 +8,14 @@ public static class VirusScannerStartup
{
public static void AddVirusScanner(this IServiceCollection services, VoidSettings settings)
{
- services.AddTransient();
+ if (settings.Postgres != default)
+ {
+ services.AddTransient();
+ }
+ else
+ {
+ services.AddTransient();
+ }
var avSettings = settings.VirusScanner;
if (avSettings != default)
@@ -28,15 +34,6 @@ public static class VirusScannerStartup
services.AddTransient();
}
- // load VirusTotal
- if (avSettings.VirusTotal != default)
- {
- loadService = true;
- services.AddTransient((svc) =>
- new VirusTotalClient(svc.GetRequiredService(), avSettings.VirusTotal));
- services.AddTransient();
- }
-
if (loadService)
{
services.AddHostedService();
diff --git a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClasses.cs b/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClasses.cs
deleted file mode 100644
index c8fe2b6..0000000
--- a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClasses.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using Newtonsoft.Json;
-// ReSharper disable InconsistentNaming
-#pragma warning disable CS8618
-
-namespace VoidCat.Services.VirusScanner.VirusTotal;
-
-public class LastAnalysisStats
-{
- [JsonProperty("confirmed-timeout")]
- public int ConfirmedTimeout { get; set; }
-
- [JsonProperty("failure")]
- public int Failure { get; set; }
-
- [JsonProperty("harmless")]
- public int Harmless { get; set; }
-
- [JsonProperty("malicious")]
- public int Malicious { get; set; }
-
- [JsonProperty("suspicious")]
- public int Suspicious { get; set; }
-
- [JsonProperty("timeout")]
- public int Timeout { get; set; }
-
- [JsonProperty("type-unsupported")]
- public int TypeUnsupported { get; set; }
-
- [JsonProperty("undetected")]
- public int Undetected { get; set; }
-}
-
-public class TotalVotes
-{
- [JsonProperty("harmless")]
- public int Harmless { get; set; }
-
- [JsonProperty("malicious")]
- public int Malicious { get; set; }
-}
-
-public class Attributes
-{
- [JsonProperty("capabilities_tags")]
- public List CapabilitiesTags { get; set; }
-
- [JsonProperty("creation_date")]
- public int CreationDate { get; set; }
-
- [JsonProperty("downloadable")]
- public bool Downloadable { get; set; }
-
- [JsonProperty("first_submission_date")]
- public int FirstSubmissionDate { get; set; }
-
- [JsonProperty("last_analysis_date")]
- public int LastAnalysisDate { get; set; }
-
- [JsonProperty("last_analysis_stats")]
- public LastAnalysisStats LastAnalysisStats { get; set; }
-
- [JsonProperty("last_modification_date")]
- public int LastModificationDate { get; set; }
-
- [JsonProperty("last_submission_date")]
- public int LastSubmissionDate { get; set; }
-
- [JsonProperty("md5")]
- public string Md5 { get; set; }
-
- [JsonProperty("meaningful_name")]
- public string MeaningfulName { get; set; }
-
- [JsonProperty("names")]
- public List Names { get; set; }
-
- [JsonProperty("reputation")]
- public int Reputation { get; set; }
-
- [JsonProperty("sha1")]
- public string Sha1 { get; set; }
-
- [JsonProperty("sha256")]
- public string Sha256 { get; set; }
-
- [JsonProperty("size")]
- public int Size { get; set; }
-
- [JsonProperty("tags")]
- public List Tags { get; set; }
-
- [JsonProperty("times_submitted")]
- public int TimesSubmitted { get; set; }
-
- [JsonProperty("total_votes")]
- public TotalVotes TotalVotes { get; set; }
-
- [JsonProperty("type_description")]
- public string TypeDescription { get; set; }
-
- [JsonProperty("type_tag")]
- public string TypeTag { get; set; }
-
- [JsonProperty("unique_sources")]
- public int UniqueSources { get; set; }
-
- [JsonProperty("vhash")]
- public string Vhash { get; set; }
-}
-
-public class Links
-{
- [JsonProperty("self")]
- public string Self { get; set; }
-}
-
-public class File
-{
- [JsonProperty("attributes")]
- public Attributes Attributes { get; set; }
-
- [JsonProperty("id")]
- public string Id { get; set; }
-
- [JsonProperty("links")]
- public Links Links { get; set; }
-
- [JsonProperty("type")]
- public string Type { get; set; }
-}
-
-public class Error
-{
- [JsonProperty("code")]
- public string Code { get; set; }
-
- [JsonProperty("message")]
- public string Message { get; set; }
-}
-
-// ReSharper disable once InconsistentNaming
-public class VTResponse
-{
- [JsonProperty("data")]
- public T Data { get; set; }
-
- [JsonProperty("error")]
- public Error Error { get; set; }
-}
-
-public class VTException : Exception
-{
- public VTException(Error error)
- {
- ErrorCode = Enum.TryParse(error.Code, out var c) ? c : VTErrorCodes.UnknownError;
- Message = error.Message;
- }
-
- public VTErrorCodes ErrorCode { get; }
-
- public string Message { get; }
-}
-
-public enum VTErrorCodes
-{
- UnknownError,
- BadRequestError,
- InvalidArgumentError,
- NotAvailableYet,
- UnselectiveContentQueryError,
- UnsupportedContentQueryError,
- AuthenticationRequiredError,
- UserNotActiveError,
- WrongCredentialsError,
- ForbiddenError,
- NotFoundError,
- AlreadyExistsError,
- FailedDependencyError,
- QuotaExceededError,
- TooManyRequestsError,
- TransientError,
- DeadlineExceededError
-}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClient.cs b/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClient.cs
deleted file mode 100644
index 09e22f1..0000000
--- a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClient.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Text;
-using Newtonsoft.Json;
-using VoidCat.Model;
-
-namespace VoidCat.Services.VirusScanner.VirusTotal;
-
-public class VirusTotalClient
-{
- private readonly HttpClient _client;
-
- public VirusTotalClient(IHttpClientFactory clientFactory, VirusTotalConfig config)
- {
- _client = clientFactory.CreateClient();
-
- _client.BaseAddress = new Uri("https://www.virustotal.com/");
- _client.DefaultRequestHeaders.Add("x-apikey", config.ApiKey);
- _client.DefaultRequestHeaders.Add("accept", "application/json");
- }
-
- public async Task GetReport(string id)
- {
- return await SendRequest(HttpMethod.Get, $"/api/v3/files/{id}");
- }
-
- private Task SendRequest(HttpMethod method, string path)
- {
- return SendRequest(method, path);
- }
-
- private async Task SendRequest(HttpMethod method, string path, TRequest? body = null)
- where TRequest : class
- {
- var req = new HttpRequestMessage(method, path);
- if (body != default)
- {
- var json = JsonConvert.SerializeObject(body);
- req.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(json));
- }
-
- var rsp = await _client.SendAsync(req);
- var rspBody = await rsp.Content.ReadAsStringAsync();
- var vtResponse = JsonConvert.DeserializeObject>(rspBody);
- if (vtResponse == default) throw new Exception("Failed?");
- if (vtResponse.Error != default) throw new VTException(vtResponse.Error);
-
- return vtResponse.Data;
- }
-}
\ No newline at end of file
diff --git a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalScanner.cs b/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalScanner.cs
deleted file mode 100644
index 6aded7f..0000000
--- a/VoidCat/Services/VirusScanner/VirusTotal/VirusTotalScanner.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Security.Cryptography;
-using VoidCat.Model;
-using VoidCat.Services.Abstractions;
-using VoidCat.Services.VirusScanner.Exceptions;
-
-namespace VoidCat.Services.VirusScanner.VirusTotal;
-
-public class VirusTotalScanner : IVirusScanner
-{
- private readonly ILogger _logger;
- private readonly VirusTotalClient _client;
- private readonly IFileStore _fileStore;
-
- public VirusTotalScanner(ILogger logger, VirusTotalClient client, IFileStore fileStore)
- {
- _logger = logger;
- _client = client;
- _fileStore = fileStore;
- }
-
- public async ValueTask ScanFile(Guid id, CancellationToken cts)
- {
- await using var fs = await _fileStore.Open(new(id, Enumerable.Empty()), cts);
-
- // hash file and check on VT
- var hash = await SHA256.Create().ComputeHashAsync(fs, cts);
-
- try
- {
- var report = await _client.GetReport(hash.ToHex());
- if (report != default)
- {
- return new()
- {
- IsVirus = report.Attributes.Reputation == 0
- };
- }
- }
- catch (VTException vx)
- {
- if (vx.ErrorCode == VTErrorCodes.QuotaExceededError)
- {
- throw new RateLimitedException()
- {
- // retry tomorrow :(
- // this makes it pretty much unusable unless you have a paid subscription
- RetryAfter = DateTimeOffset.Now.Date.AddDays(1)
- };
- }
-
- throw;
- }
-
- throw new InvalidOperationException();
- }
-}
\ No newline at end of file
diff --git a/VoidCat/spa/src/Api.js b/VoidCat/spa/src/Api.js
index d24217a..b33b33e 100644
--- a/VoidCat/spa/src/Api.js
+++ b/VoidCat/spa/src/Api.js
@@ -31,7 +31,7 @@ export function useApi() {
},
Api: {
info: () => getJson("GET", "/info"),
- fileInfo: (id) => getJson("GET", `/upload/${id}`),
+ fileInfo: (id) => getJson("GET", `/upload/${id}`, undefined, auth),
setPaywallConfig: (id, cfg) => getJson("POST", `/upload/${id}/paywall`, cfg, auth),
createOrder: (id) => getJson("GET", `/upload/${id}/paywall`),
getOrder: (file, order) => getJson("GET", `/upload/${file}/paywall/${order}`),
diff --git a/VoidCat/spa/src/FileEdit.js b/VoidCat/spa/src/FileEdit.js
index 34683e8..ddc904d 100644
--- a/VoidCat/spa/src/FileEdit.js
+++ b/VoidCat/spa/src/FileEdit.js
@@ -16,8 +16,8 @@ export function FileEdit(props) {
const [name, setName] = useState(meta?.name);
const [description, setDescription] = useState(meta?.description);
- const privateFile = profile?.id === meta?.uploader ? file : JSON.parse(window.localStorage.getItem(file.id));
- if (!privateFile) {
+ const privateFile = profile?.id === file?.uploader?.id ? file : JSON.parse(window.localStorage.getItem(file.id));
+ if (!privateFile || privateFile?.metadata?.editSecret === null) {
return null;
}
diff --git a/VoidCat/spa/src/FilePreview.css b/VoidCat/spa/src/FilePreview.css
index 676d3d9..ab61828 100644
--- a/VoidCat/spa/src/FilePreview.css
+++ b/VoidCat/spa/src/FilePreview.css
@@ -25,4 +25,5 @@
padding: 10px;
border-radius: 10px;
border: 1px solid red;
+ margin-bottom: 5px;
}
\ No newline at end of file
diff --git a/VoidCat/spa/src/FilePreview.js b/VoidCat/spa/src/FilePreview.js
index 983acf2..87c1277 100644
--- a/VoidCat/spa/src/FilePreview.js
+++ b/VoidCat/spa/src/FilePreview.js
@@ -118,7 +118,7 @@ export function FilePreview() {
Detected as:
- {scanResult.virusNames.join('\n')}
+ {scanResult.names}
);
diff --git a/VoidCat/spa/src/FooterLinks.js b/VoidCat/spa/src/FooterLinks.js
index 37c6aab..50de0d7 100644
--- a/VoidCat/spa/src/FooterLinks.js
+++ b/VoidCat/spa/src/FooterLinks.js
@@ -1,12 +1,22 @@
import "./FooterLinks.css"
import StrikeLogo from "./image/strike.png";
+import {useSelector} from "react-redux";
+
+export function FooterLinks() {
+ const profile = useSelector(state => state.login.profile);
-export function FooterLinks(){
return (
);
}
\ No newline at end of file
diff --git a/VoidCat/spa/src/Login.js b/VoidCat/spa/src/Login.js
index 6238f29..c6a018e 100644
--- a/VoidCat/spa/src/Login.js
+++ b/VoidCat/spa/src/Login.js
@@ -12,7 +12,7 @@ export function Login() {
const [password, setPassword] = useState();
const [error, setError] = useState();
const [captchaResponse, setCaptchaResponse] = useState();
- const captchaKey = useSelector(state => state.info.stats.captchaSiteKey);
+ const captchaKey = useSelector(state => state.info.stats?.captchaSiteKey);
const dispatch = useDispatch();
async function login(fnLogin) {
@@ -34,7 +34,8 @@ export function Login() {
Login
- Username:
- - setUsername(e.target.value)} placeholder="user@example.com"/>
+ - setUsername(e.target.value)} placeholder="user@example.com"/>
+
- Password:
- setPassword(e.target.value)}/>