From d7d092bc633f89a617885810e2af1e0d44499bef Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 8 Mar 2022 13:47:42 +0000 Subject: [PATCH] Add swagger and docs --- .gitignore | 3 +- VoidCat/Controllers/Admin/AdminController.cs | 14 ++++++ VoidCat/Controllers/AuthController.cs | 10 ++++ VoidCat/Controllers/DownloadController.cs | 4 ++ VoidCat/Controllers/StatsController.cs | 10 ++++ VoidCat/Controllers/UploadController.cs | 48 +++++++++++++++++++ VoidCat/Controllers/UserController.cs | 40 +++++++++++++++- .../Exceptions/VoidFileNotFoundException.cs | 3 ++ .../Exceptions/VoidInvalidIdException.cs | 17 +++++++ .../Exceptions/VoidNotAllowedException.cs | 3 ++ VoidCat/Model/Extensions.cs | 6 ++- VoidCat/Program.cs | 37 +++++++++++++- VoidCat/Services/Users/UserStore.cs | 5 +- VoidCat/VoidCat.csproj | 1 + 14 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 VoidCat/Model/Exceptions/VoidInvalidIdException.cs 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