diff --git a/.gitignore b/.gitignore
index bb1d98d..657bd7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@ out/
sw.js
.DS_Store
.idea/
-appsettings.*.json
\ No newline at end of file
+appsettings.*.json
+VoidCat.xml
\ No newline at end of file
diff --git a/VoidCat/Controllers/Admin/AdminController.cs b/VoidCat/Controllers/Admin/AdminController.cs
index 81298c3..d69726e 100644
--- a/VoidCat/Controllers/Admin/AdminController.cs
+++ b/VoidCat/Controllers/Admin/AdminController.cs
@@ -20,6 +20,11 @@ public class AdminController : Controller
_fileInfo = fileInfo;
}
+ ///
+ /// List all files in the system
+ ///
+ /// Page request
+ ///
[HttpPost]
[Route("file")]
public async Task> ListFiles([FromBody] PagedRequest request)
@@ -27,6 +32,10 @@ public class AdminController : Controller
return await (await _fileStore.ListFiles(request)).GetResults();
}
+ ///
+ /// Delete a file from the system
+ ///
+ /// Id of the file to delete
[HttpDelete]
[Route("file/{id}")]
public async Task DeleteFile([FromRoute] string id)
@@ -36,6 +45,11 @@ public class AdminController : Controller
await _fileInfo.Delete(gid);
}
+ ///
+ /// List all users in the system
+ ///
+ /// Page request
+ ///
[HttpPost]
[Route("user")]
public async Task> ListUsers([FromBody] PagedRequest request)
diff --git a/VoidCat/Controllers/AuthController.cs b/VoidCat/Controllers/AuthController.cs
index 6cf6833..4b06a63 100644
--- a/VoidCat/Controllers/AuthController.cs
+++ b/VoidCat/Controllers/AuthController.cs
@@ -22,6 +22,11 @@ public class AuthController : Controller
_settings = settings;
}
+ ///
+ /// Login to a user account
+ ///
+ ///
+ ///
[HttpPost]
[Route("login")]
public async Task Login([FromBody] LoginRequest req)
@@ -45,6 +50,11 @@ public class AuthController : Controller
}
}
+ ///
+ /// Register a new account
+ ///
+ ///
+ ///
[HttpPost]
[Route("register")]
public async Task Register([FromBody] LoginRequest req)
diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs
index 5391154..278fb0e 100644
--- a/VoidCat/Controllers/DownloadController.cs
+++ b/VoidCat/Controllers/DownloadController.cs
@@ -31,6 +31,10 @@ public class DownloadController : Controller
return SetupDownload(gid);
}
+ ///
+ /// Download a specific file by Id
+ ///
+ ///
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 86400)]
[HttpGet]
[Route("{id}")]
diff --git a/VoidCat/Controllers/StatsController.cs b/VoidCat/Controllers/StatsController.cs
index 80ddf61..7debfe0 100644
--- a/VoidCat/Controllers/StatsController.cs
+++ b/VoidCat/Controllers/StatsController.cs
@@ -16,6 +16,11 @@ namespace VoidCat.Controllers
_fileStore = fileStore;
}
+
+ ///
+ /// Return system info
+ ///
+ ///
[HttpGet]
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60)]
public async Task GetGlobalStats()
@@ -33,6 +38,11 @@ namespace VoidCat.Controllers
return new(bw, bytes, count, BuildInfo.GetBuildInfo());
}
+ ///
+ /// Get stats for a specific file
+ ///
+ ///
+ ///
[HttpGet]
[Route("{id}")]
public async Task GetFileStats([FromRoute] string id)
diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs
index de0c698..8f31666 100644
--- a/VoidCat/Controllers/UploadController.cs
+++ b/VoidCat/Controllers/UploadController.cs
@@ -27,6 +27,20 @@ namespace VoidCat.Controllers
_fileInfo = fileInfo;
}
+ ///
+ /// Primary upload endpoint
+ ///
+ ///
+ /// Additional optional headers can be included to provide details about the file being uploaded:
+ ///
+ /// `V-Content-Type` - Sets the `mimeType` of the file and is used on the preview page to display the file.
+ /// `V-Filename` - Sets the filename of the file.
+ /// `V-Description` - Sets the description of the file.
+ /// `V-Full-Digest` - Include a SHA256 hash of the entire file for verification purposes.
+ /// `V-Digest` - A SHA256 hash of the data you are sending in this request.
+ ///
+ /// True if you want to return only the url of the file in the response
+ /// Returns
[HttpPost]
[DisableRequestSizeLimit]
[DisableFormValueModelBinding]
@@ -67,6 +81,18 @@ namespace VoidCat.Controllers
}
}
+ ///
+ /// Append data onto a file
+ ///
+ ///
+ /// This endpoint is mainly used to bypass file upload limits enforced by CloudFlare.
+ /// Clients should split their uploads into segments, upload the first segment to the regular
+ /// upload endpoint, use the `editSecret` to append data to the file.
+ ///
+ /// Set the edit secret in the header `V-EditSecret` otherwise you will not be able to append data.
+ ///
+ ///
+ ///
[HttpPost]
[DisableRequestSizeLimit]
[DisableFormValueModelBinding]
@@ -97,6 +123,11 @@ namespace VoidCat.Controllers
}
}
+ ///
+ /// Return information about a specific file
+ ///
+ ///
+ ///
[HttpGet]
[Route("{id}")]
public ValueTask GetInfo([FromRoute] string id)
@@ -104,6 +135,11 @@ namespace VoidCat.Controllers
return _fileInfo.Get(id.FromBase58Guid());
}
+ ///
+ /// Create a paywall order to pay
+ ///
+ /// File id
+ ///
[HttpGet]
[Route("{id}/paywall")]
public async ValueTask CreateOrder([FromRoute] string id)
@@ -116,6 +152,12 @@ namespace VoidCat.Controllers
return await provider.CreateOrder(file!);
}
+ ///
+ /// Return the status of an order
+ ///
+ /// File id
+ /// Order id
+ ///
[HttpGet]
[Route("{id}/paywall/{order:guid}")]
public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
@@ -127,6 +169,12 @@ namespace VoidCat.Controllers
return await provider.GetOrderStatus(order);
}
+ ///
+ /// Update the paywall config
+ ///
+ /// File id
+ /// Requested config to set on the file
+ ///
[HttpPost]
[Route("{id}/paywall")]
public async Task SetPaywallConfig([FromRoute] string id, [FromBody] SetPaywallConfigRequest req)
diff --git a/VoidCat/Controllers/UserController.cs b/VoidCat/Controllers/UserController.cs
index 7cbc06d..39fd734 100644
--- a/VoidCat/Controllers/UserController.cs
+++ b/VoidCat/Controllers/UserController.cs
@@ -18,13 +18,23 @@ public class UserController : Controller
_emailVerification = emailVerification;
}
+ ///
+ /// Return user profile
+ ///
+ ///
+ /// You do not need to be logged in to return a user profile if their profile is set to public.
+ ///
+ /// You may also use `me` as the `id` to get the logged in users profile.
+ ///
+ /// User id to load
+ ///
[HttpGet]
public async Task GetUser([FromRoute] string id)
{
var loggedUser = HttpContext.GetUserId();
var isMe = id.Equals("me", StringComparison.InvariantCultureIgnoreCase);
if (isMe && !loggedUser.HasValue) return Unauthorized();
-
+
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
if (loggedUser == requestedId)
{
@@ -39,6 +49,13 @@ public class UserController : Controller
return Json(user);
}
+ ///
+ /// Update profile settings
+ ///
+ ///
+ /// User id
+ ///
+ ///
[HttpPost]
public async Task UpdateUser([FromRoute] string id, [FromBody] PublicVoidUser user)
{
@@ -51,6 +68,16 @@ public class UserController : Controller
return Ok();
}
+ ///
+ /// Return a list of files which the user has uploaded
+ ///
+ ///
+ /// This will return files if the profile has public uploads set on their profile.
+ /// Otherwise you can return your own uploaded files if you are logged in.
+ ///
+ /// User id
+ /// Page request
+ ///
[HttpPost]
[Route("files")]
public async Task ListUserFiles([FromRoute] string id,
@@ -71,6 +98,11 @@ public class UserController : Controller
return Json(await results.GetResults());
}
+ ///
+ /// Send a verification code for a specific user
+ ///
+ /// User id to send code for
+ ///
[HttpGet]
[Route("verify")]
public async Task SendVerificationCode([FromRoute] string id)
@@ -85,6 +117,12 @@ public class UserController : Controller
return Accepted();
}
+ ///
+ /// Confirm email verification code
+ ///
+ /// User id to verify
+ /// Verification code to check
+ ///
[HttpPost]
[Route("verify")]
public async Task VerifyCode([FromRoute] string id, [FromBody] string code)
diff --git a/VoidCat/Model/Exceptions/VoidFileNotFoundException.cs b/VoidCat/Model/Exceptions/VoidFileNotFoundException.cs
index 0bb456b..561f9e0 100644
--- a/VoidCat/Model/Exceptions/VoidFileNotFoundException.cs
+++ b/VoidCat/Model/Exceptions/VoidFileNotFoundException.cs
@@ -1,5 +1,8 @@
namespace VoidCat.Model.Exceptions;
+///
+/// Specified file was not found
+///
public class VoidFileNotFoundException : Exception
{
public VoidFileNotFoundException(Guid id)
diff --git a/VoidCat/Model/Exceptions/VoidInvalidIdException.cs b/VoidCat/Model/Exceptions/VoidInvalidIdException.cs
new file mode 100644
index 0000000..589c5fd
--- /dev/null
+++ b/VoidCat/Model/Exceptions/VoidInvalidIdException.cs
@@ -0,0 +1,17 @@
+namespace VoidCat.Model.Exceptions;
+
+///
+/// Specified id was not in the correct format
+///
+public class VoidInvalidIdException : Exception
+{
+ public VoidInvalidIdException(string id)
+ {
+ Id = id;
+ }
+
+ ///
+ /// The id in question
+ ///
+ public string Id { get; }
+}
\ No newline at end of file
diff --git a/VoidCat/Model/Exceptions/VoidNotAllowedException.cs b/VoidCat/Model/Exceptions/VoidNotAllowedException.cs
index bad2d94..0d11d03 100644
--- a/VoidCat/Model/Exceptions/VoidNotAllowedException.cs
+++ b/VoidCat/Model/Exceptions/VoidNotAllowedException.cs
@@ -1,5 +1,8 @@
namespace VoidCat.Model.Exceptions;
+///
+/// Operation is not allowed
+///
public class VoidNotAllowedException : Exception
{
public VoidNotAllowedException(string message) : base(message)
diff --git a/VoidCat/Model/Extensions.cs b/VoidCat/Model/Extensions.cs
index 8e2406f..ec71a8f 100644
--- a/VoidCat/Model/Extensions.cs
+++ b/VoidCat/Model/Extensions.cs
@@ -1,10 +1,10 @@
-using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
+using VoidCat.Model.Exceptions;
namespace VoidCat.Model;
@@ -42,7 +42,9 @@ public static class Extensions
public static Guid FromBase58Guid(this string base58)
{
var enc = new NBitcoin.DataEncoders.Base58Encoder();
- return new Guid(enc.DecodeData(base58));
+ var guidBytes = enc.DecodeData(base58);
+ if (guidBytes.Length != 16) throw new VoidInvalidIdException(base58);
+ return new Guid(guidBytes);
}
public static string ToBase58(this Guid id)
diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs
index 1f426eb..a115908 100644
--- a/VoidCat/Program.cs
+++ b/VoidCat/Program.cs
@@ -1,6 +1,8 @@
+using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Prometheus;
using StackExchange.Redis;
@@ -48,6 +50,32 @@ if (useRedis)
}
services.AddHttpClient();
+services.AddSwaggerGen(c =>
+{
+ c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
+ {
+ In = ParameterLocation.Header,
+ Description = "Please insert JWT with Bearer into field",
+ Name = "Authorization",
+ Type = SecuritySchemeType.ApiKey
+ });
+ c.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ }
+ },
+ new string[] { }
+ }
+ });
+ var path = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
+ c.IncludeXmlComments(path);
+});
services.AddCors(opt =>
{
opt.AddDefaultPolicy(p =>
@@ -76,7 +104,10 @@ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
};
});
-services.AddAuthorization((opt) => { opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); }); });
+services.AddAuthorization((opt) =>
+{
+ opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); });
+});
// void.cat services
//
@@ -135,6 +166,8 @@ app.UseStaticFiles();
app.UseRouting();
app.UseCors();
+app.UseSwagger();
+app.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
@@ -148,4 +181,4 @@ app.UseEndpoints(ep =>
#endif
});
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/VoidCat/Services/Users/UserStore.cs b/VoidCat/Services/Users/UserStore.cs
index 22ce819..6e0b010 100644
--- a/VoidCat/Services/Users/UserStore.cs
+++ b/VoidCat/Services/Users/UserStore.cs
@@ -86,9 +86,12 @@ public class UserStore : IUserStore
var oldUser = await Get(newUser.Id);
if (oldUser == null) return;
+ //retain flags
+ var isEmailVerified = oldUser.Flags.HasFlag(VoidUserFlags.EmailVerified);
+
// update only a few props
oldUser.Avatar = newUser.Avatar;
- oldUser.Flags = newUser.Flags;
+ oldUser.Flags = newUser.Flags | (isEmailVerified ? VoidUserFlags.EmailVerified : 0);
oldUser.DisplayName = newUser.DisplayName;
await Set(newUser.Id, oldUser);
diff --git a/VoidCat/VoidCat.csproj b/VoidCat/VoidCat.csproj
index 3e64f36..0143408 100644
--- a/VoidCat/VoidCat.csproj
+++ b/VoidCat/VoidCat.csproj
@@ -9,6 +9,7 @@
$(DefaultItemExcludes);$(SpaRoot)node_modules\**
True
$(DefineConstants);HostSPA
+ $(AssemblyName).xml