From 0406903aa76ac9eb91ebe7d3dd753f9d79bd5ffc Mon Sep 17 00:00:00 2001 From: JacobTech Date: Sun, 2 Apr 2023 16:44:26 -0400 Subject: [PATCH] Init --- .gitignore | 15 + LuskiServer.sln | 16 + .../ActionFilters/TokenFilterAttribute.cs | 29 + LuskiServer/Classes/AppConfig.cs | 29 + LuskiServer/Classes/Commands/Command.cs | 10 + LuskiServer/Classes/EXT.cs | 144 +++ LuskiServer/Classes/HTTPResponse.cs | 17 + LuskiServer/Classes/Login.cs | 6 + LuskiServer/Classes/Luski.cs | 397 +++++++++ LuskiServer/Classes/PluginWarningAttribute.cs | 26 + LuskiServer/Classes/TableDef/Categories.cs | 14 + LuskiServer/Classes/TableDef/Channels.cs | 15 + LuskiServer/Classes/TableDef/Files.cs | 12 + LuskiServer/Classes/TableDef/Logs.cs | 11 + LuskiServer/Classes/TableDef/Messages.cs | 13 + LuskiServer/Classes/TableDef/Roles.cs | 13 + .../Classes/TableDef/ServerRoleOverides.cs | 11 + LuskiServer/Classes/TableDef/SessionTokens.cs | 12 + .../Classes/TableDef/UserRoleOverides.cs | 12 + LuskiServer/Classes/TableDef/Users.cs | 24 + LuskiServer/Classes/Tables.cs | 17 + LuskiServer/ConfigureSwaggerOptions.cs | 90 ++ .../Controllers/v1/CreateAccountController.cs | 172 ++++ LuskiServer/Controllers/v1/KeysController.cs | 67 ++ .../v1/SocketUserProfileController.cs | 163 ++++ LuskiServer/Enums/ChannelType.cs | 6 + LuskiServer/Enums/CommandServiceType.cs | 9 + LuskiServer/Enums/ErrorCode.cs | 14 + LuskiServer/Enums/LogType.cs | 6 + LuskiServer/Enums/PictureType.cs | 13 + LuskiServer/Enums/PluginPerms.cs | 15 + LuskiServer/Enums/ServerPermissions.cs | 29 + LuskiServer/Enums/Status.cs | 11 + LuskiServer/Enums/WorkerId.cs | 14 + LuskiServer/Interfaces/ICommand.cs | 11 + LuskiServer/Interfaces/IPlugin.cs | 16 + LuskiServer/LuskiServer.csproj | 29 + LuskiServer/Program.cs | 206 +++++ LuskiServer/Properties/launchSettings.json | 41 + LuskiServer/SwaggerDefaultValues.cs | 65 ++ LuskiServer/appsettings.Development.json | 9 + LuskiServer/appsettings.json | 9 + LuskiServer/www/css/SwaggerDark.css | 829 ++++++++++++++++++ 43 files changed, 2667 insertions(+) create mode 100644 LuskiServer.sln create mode 100644 LuskiServer/Classes/ActionFilters/TokenFilterAttribute.cs create mode 100644 LuskiServer/Classes/AppConfig.cs create mode 100644 LuskiServer/Classes/Commands/Command.cs create mode 100644 LuskiServer/Classes/EXT.cs create mode 100644 LuskiServer/Classes/HTTPResponse.cs create mode 100644 LuskiServer/Classes/Login.cs create mode 100644 LuskiServer/Classes/Luski.cs create mode 100644 LuskiServer/Classes/PluginWarningAttribute.cs create mode 100644 LuskiServer/Classes/TableDef/Categories.cs create mode 100644 LuskiServer/Classes/TableDef/Channels.cs create mode 100644 LuskiServer/Classes/TableDef/Files.cs create mode 100644 LuskiServer/Classes/TableDef/Logs.cs create mode 100644 LuskiServer/Classes/TableDef/Messages.cs create mode 100644 LuskiServer/Classes/TableDef/Roles.cs create mode 100644 LuskiServer/Classes/TableDef/ServerRoleOverides.cs create mode 100644 LuskiServer/Classes/TableDef/SessionTokens.cs create mode 100644 LuskiServer/Classes/TableDef/UserRoleOverides.cs create mode 100644 LuskiServer/Classes/TableDef/Users.cs create mode 100644 LuskiServer/Classes/Tables.cs create mode 100644 LuskiServer/ConfigureSwaggerOptions.cs create mode 100644 LuskiServer/Controllers/v1/CreateAccountController.cs create mode 100644 LuskiServer/Controllers/v1/KeysController.cs create mode 100644 LuskiServer/Controllers/v1/SocketUserProfileController.cs create mode 100644 LuskiServer/Enums/ChannelType.cs create mode 100644 LuskiServer/Enums/CommandServiceType.cs create mode 100644 LuskiServer/Enums/ErrorCode.cs create mode 100644 LuskiServer/Enums/LogType.cs create mode 100644 LuskiServer/Enums/PictureType.cs create mode 100644 LuskiServer/Enums/PluginPerms.cs create mode 100644 LuskiServer/Enums/ServerPermissions.cs create mode 100644 LuskiServer/Enums/Status.cs create mode 100644 LuskiServer/Enums/WorkerId.cs create mode 100644 LuskiServer/Interfaces/ICommand.cs create mode 100644 LuskiServer/Interfaces/IPlugin.cs create mode 100644 LuskiServer/LuskiServer.csproj create mode 100644 LuskiServer/Program.cs create mode 100644 LuskiServer/Properties/launchSettings.json create mode 100644 LuskiServer/SwaggerDefaultValues.cs create mode 100644 LuskiServer/appsettings.Development.json create mode 100644 LuskiServer/appsettings.json create mode 100644 LuskiServer/www/css/SwaggerDark.css diff --git a/.gitignore b/.gitignore index 57940fd..d4375a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf +.idea/**/.idea/** +.idea/**/.idea/ # AWS User-specific .idea/**/aws.xml @@ -77,3 +79,16 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ diff --git a/LuskiServer.sln b/LuskiServer.sln new file mode 100644 index 0000000..449e486 --- /dev/null +++ b/LuskiServer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuskiServer", "LuskiServer\LuskiServer.csproj", "{80FFB3F8-03AC-450C-AA89-0F01A681AFEC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80FFB3F8-03AC-450C-AA89-0F01A681AFEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80FFB3F8-03AC-450C-AA89-0F01A681AFEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80FFB3F8-03AC-450C-AA89-0F01A681AFEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80FFB3F8-03AC-450C-AA89-0F01A681AFEC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/LuskiServer/Classes/ActionFilters/TokenFilterAttribute.cs b/LuskiServer/Classes/ActionFilters/TokenFilterAttribute.cs new file mode 100644 index 0000000..1978f29 --- /dev/null +++ b/LuskiServer/Classes/ActionFilters/TokenFilterAttribute.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace LuskiServer.Classes.ActionFilters; + +[AttributeUsage(AttributeTargets.Method)] +public class TokenFilterAttribute : Attribute, IActionFilter +{ + public void OnActionExecuting(ActionExecutingContext context) + { + //token check here + Console.WriteLine("Token Check"); + } + + public void OnActionExecuted(ActionExecutedContext context) + { + + } + + private string name; + public double verstion; + + public TokenFilterAttribute() + { + name = "token"; + verstion = 1.0; + } +} \ No newline at end of file diff --git a/LuskiServer/Classes/AppConfig.cs b/LuskiServer/Classes/AppConfig.cs new file mode 100644 index 0000000..aebde24 --- /dev/null +++ b/LuskiServer/Classes/AppConfig.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; + +namespace LuskiServer.Classes; + +public class AppConfig +{ + [JsonInclude] + [JsonPropertyName("address")] + public string Address { get; set; } = "127.0.0.1"; + [JsonInclude] + [JsonPropertyName("database")] + public string Database { get; set; } = "Some database name"; + [JsonInclude] + [JsonPropertyName("username")] + public string Username { get; set; } = "Some postgresql username"; + [JsonInclude] + [JsonPropertyName("password")] + public string Password { get; set; } = "Some postgresql password"; + [JsonInclude] + [JsonPropertyName("connection_name")] + public string CustomeName { get; set; } = "Luski Server"; +} + +[JsonSerializable(typeof(AppConfig))] +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never, GenerationMode = JsonSourceGenerationMode.Default)] +internal partial class AppConfigContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/LuskiServer/Classes/Commands/Command.cs b/LuskiServer/Classes/Commands/Command.cs new file mode 100644 index 0000000..abd5e97 --- /dev/null +++ b/LuskiServer/Classes/Commands/Command.cs @@ -0,0 +1,10 @@ +using LuskiServer.Enums; + +namespace LuskiServer.Classes.Commands; + +public class Command +{ + public string Name { get; set; } = default!; + public long ServiceID { get; set; } = default!; + public CommandServiceType Type { get; set; } +} \ No newline at end of file diff --git a/LuskiServer/Classes/EXT.cs b/LuskiServer/Classes/EXT.cs new file mode 100644 index 0000000..6bb4e14 --- /dev/null +++ b/LuskiServer/Classes/EXT.cs @@ -0,0 +1,144 @@ +using System.Diagnostics; +using System.Net.Mime; +using System.Text; +using LuskiServer.Classes.TableDef; +using LuskiServer.Enums; +using LuskiServer.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace LuskiServer.Classes; + +public static class EXT +{/* + public static bool CanTokenRequest(this ControllerBase Base, out long id, out TResult? result) where TResult : HTTPResponse, new() + { + if (Base.Request.Headers.ContainsKey("token")) + { + return ValidateToke(Base, out id, out result); + } + result = Base.ShowError(ErrorCode.MissingToken); + id = 0; + return false; + } + */ + + + public static string GetNumberString(this ServerPermissions enu) + { + return ((long)(enu)).ToString(); + } + + public static bool CanTokenRequest(this ControllerBase Base, out long id, out IActionResult? result) + { + if (Base.Request.Headers.ContainsKey("token")) + { + return ValidateToke(Base, out id, out result); + } + result = Base.ShowError(ErrorCode.MissingToken, "You did not provide a token"); + id = 0; + return false; + } + + public static IActionResult ShowError(this ControllerBase Base, Exception Error, bool LogInDB = true) + { + if (LogInDB) + { + Tables.Logs.Insert( + Logs.ID.CreateParameter(Luski.Snowflake.GenerateSnowflake(WorkerId.Log).ID), + Logs.Type.CreateParameter(LogType.Error), + Logs.Message.CreateParameter(Error.ToString())); + } + + return Base.ShowError(ErrorCode.ServerError, Error.Message); + } + + public static IActionResult ShowError(this ControllerBase Base, ErrorCode code, string Error) + { + return Base.StatusCode(403, new HTTPResponse() + { + error = code, + error_message = Error + }); + } + /* + public static TReturn ShowError(this ControllerBase Base, ErrorCode code, Exception? Error, bool LogInDB = true) where TReturn : HTTPResponse, new() + { + if (Error is null) return new TReturn() + { + error = ErrorCode.ServerError, + error_message = "Something Went wrong in the error handler." + }; + + try + { + StackTrace st = new(Error, true); + StackFrame? frame = st.GetFrame(0); + string? controler = Base.RouteData.Values["controller"]?.ToString(); + string Line = "'Unknown'"; + if (frame is not null) Line = frame.GetFileLineNumber().ToString(); + string msg = $"In '{controler}Controller.cs' on line '{Line}' through error '{Error.Message}' and ST'{st}' and ERROR:\n'{Error}' FRAM:\n '{frame}'"; + //if (LogInDB) Log(msg, LogType.Error); + return Base.ShowError(code, Error.Message); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return new TReturn() + { + error = ErrorCode.ServerError, + error_message = "Something Went wrong in the error handler" + }; + + } + } + + public static TReturn ShowError(this ControllerBase Base, ErrorCode code, string? Error = null) where TReturn : HTTPResponse, new() + { + Base.Response.ContentType = "application/json"; + switch (code) + { + case ErrorCode.Forbidden or ErrorCode.InvalidHeader or ErrorCode.InvalidToken or ErrorCode.MissingToken or ErrorCode.MissingToken or ErrorCode.MissingHeader: + Base.Response.StatusCode = StatusCodes.Status403Forbidden; + break; + } + TReturn hTTPResponse = new() + { + error = code, + error_message = Error + }; + return hTTPResponse; + } + */ + private static bool CheckToken(string Token, ref long ID) + { + try + { + string id = Encoding.UTF8.GetString(Convert.FromBase64String(Token.Split('.')[0])); + string? tok = Tables.Users.Read(Users.Token, Users.ID.CreateParameter(long.Parse(id))); + if (!string.IsNullOrWhiteSpace(tok) && tok == Token) + { + ID = long.Parse(id); + return true; + } + return false; + } + catch + { + return false; + } + } + + private static bool ValidateToke(ControllerBase Base, out long Id, out IActionResult? result) + { + long id = 0; + if (CheckToken(Base.Request.Headers["token"].First()!, ref id)) + { + result = null; + Id = id; + return true; + } + Id = id; + result = Base.ShowError(ErrorCode.InvalidToken, (string)null!); + return false; + } +} \ No newline at end of file diff --git a/LuskiServer/Classes/HTTPResponse.cs b/LuskiServer/Classes/HTTPResponse.cs new file mode 100644 index 0000000..5c993b8 --- /dev/null +++ b/LuskiServer/Classes/HTTPResponse.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using LuskiServer.Enums; + +namespace LuskiServer.Classes; + +public class HTTPResponse +{ + public ErrorCode? error { get; set; } = default!; + public string? error_message { get; set; } = default!; +} + + +[JsonSerializable(typeof(HTTPResponse))] +internal partial class HTTPResponseContext : JsonSerializerContext +{ + +} \ No newline at end of file diff --git a/LuskiServer/Classes/Login.cs b/LuskiServer/Classes/Login.cs new file mode 100644 index 0000000..4eaad2f --- /dev/null +++ b/LuskiServer/Classes/Login.cs @@ -0,0 +1,6 @@ +namespace LuskiServer.Classes; + +public class Login +{ + public string login_token { get; set; } = default!; +} \ No newline at end of file diff --git a/LuskiServer/Classes/Luski.cs b/LuskiServer/Classes/Luski.cs new file mode 100644 index 0000000..b8e0cdb --- /dev/null +++ b/LuskiServer/Classes/Luski.cs @@ -0,0 +1,397 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes; + +public static class Luski +{ + public static Database Database = null!; + + public static TResult GetSettings(string path, JsonTypeInfo TypeInfo, bool EndOnError = false) where TResult : new() + { + TResult? @out; + if (!File.Exists(path)) + { + @out = new(); + try + { + File.WriteAllText(path, JsonSerializer.Serialize(@out, TypeInfo)); + } + catch (Exception e) + { + Console.WriteLine("cant write file at '{0}'. make sure the premisions are set", path); + if (EndOnError) Environment.Exit(0); + } + } + + try + { + string Fil = ""; + try + { + Fil = File.ReadAllText(path); + } + catch (Exception e) + { + Console.WriteLine("cant read file at '{0}'. make sure the premisions are set", path); + if (EndOnError) Environment.Exit(0); + } + @out = JsonSerializer.Deserialize(Fil, TypeInfo); + if (@out is null) + { + @out = new(); + } + } + catch + { + @out = new(); + } + + try + { + File.WriteAllText(path, JsonSerializer.Serialize(@out, TypeInfo)); + } + catch (Exception e) + { + Console.WriteLine("cant write file at '{0}'. make sure the premisions are set", path); + if (EndOnError) Environment.Exit(0); + } + return @out; + } + + public static AppConfig Config = null!; + + public static class Info + { + public static class Routes + { + public static class Default + { + public const string Base = "v{version:apiVersion}/[controller]"; + public const string BaseID = $"{Base}/{{id?}}"; + public const string Action = $"{Base}/{{action?}}"; + public const string ActionID = $"{Action}/{{id?}}"; + } + } + + public static readonly DateTime Epoch = new(2023, 1, 1, 0, 0, 0, 0); + + public static long Timestamp + { + get + { + double ts = Math.Round(DateTime.Now.Subtract(Epoch).TotalMilliseconds, 0); + return long.Parse(ts.ToString().Replace(".", string.Empty)); + } + } + } + + public class Snowflake + { + public Snowflake(long ID) + { + this.ID = ID; + Increment = (ushort)((ID << 52) >> 52); + Worker_ID = (ushort)((ID << 47) >> 59); + Server_ID = (ushort)((ID << 42) >> 59); + Timestamp = ID >> 22; + } + + public static Snowflake GenerateSnowflake(WorkerId workerID) + { + i++; + if (i > 4096) i = 0; + return new Snowflake((((((Info.Timestamp << 5) | (ushort)0) << 5) | (ushort)workerID) << 12) | i); + } + + public long ID { get; } + public long Timestamp { get; } + public ushort Worker_ID { get; } + public ushort Server_ID { get; } + public ushort Increment { get; } + + private static ushort i = 0; + } + + public static class Encryption + { + public class AES + { + public static byte[] Encrypt(byte[] data, string Password) + { + byte[] salt = RandomNumberGenerator.GetBytes(100); + byte[] passwordBytes = Encoding.UTF8.GetBytes(Password); + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + byte[] encrypted; + + using Aes aesAlg = Aes.Create(); + aesAlg.KeySize = 256; + aesAlg.BlockSize = 128; + aesAlg.Padding = PaddingMode.PKCS7; + aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); + aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8); + + ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); + + using MemoryStream msEncrypt = new(); + msEncrypt.Write(salt, 0, salt.Length); + using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write); + csEncrypt.Write(data, 0, data.Length); + csEncrypt.Dispose(); + encrypted = msEncrypt.ToArray(); + return encrypted; + } + + public static byte[] Decrypt(byte[] data, string Password) + { + byte[] salt = new byte[100]; + using MemoryStream fsCrypt = new(data); + fsCrypt.Read(salt, 0, salt.Length); + byte[] passwordBytes = Encoding.UTF8.GetBytes(Password); + Rfc2898DeriveBytes key = new(passwordBytes, salt, 50000); + byte[] decrypted = new byte[data.Length - salt.Length]; + + using Aes aesAlg = Aes.Create(); + aesAlg.KeySize = 256; + aesAlg.BlockSize = 128; + aesAlg.Padding = PaddingMode.PKCS7; + aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); + aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8); + + ICryptoTransform encryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); + + using CryptoStream csEncrypt = new(fsCrypt, encryptor, CryptoStreamMode.Read); + MemoryStream fsOut = new(); + int read; + byte[] buffer = new byte[data.Length]; + while ((read = csEncrypt.Read(buffer, 0, buffer.Length)) > 0) + { + fsOut.Write(buffer, 0, read); + } + csEncrypt.Dispose(); + fsCrypt.Dispose(); + decrypted = fsOut.ToArray(); + fsOut.Dispose(); + return decrypted; + } + } + + public static byte[] CalculateHash(string text, byte[] salt, int iteration) + { + Rfc2898DeriveBytes? pbkdf2 = new(text, salt, iteration); + return pbkdf2.GetBytes(64); + } + + public static byte[] Hash(byte[] data, byte[]? salt = null) + { + using SHA256 sha = SHA256.Create(); + if (salt is null) return sha.ComputeHash(data); + else return sha.ComputeHash(Combine(data, salt)); + } + + internal const int PasswordVersion = 0; + + internal static byte[] RemotePasswordEncrypt(string Base64Password, byte[] salt, int PasswordVersion = PasswordVersion) + { + return PasswordVersion switch + { + 0 => Hash(Decrypt(Convert.FromBase64String(Base64Password), Keys.PrivateKey), salt), + _ => throw new ArgumentException("The value provided was not accepted", nameof(PasswordVersion)), + }; + } + + public static class Keys + { + private static readonly RSACryptoServiceProvider RSA = new(4096); + private static RSAParameters? Priv = null; + private static string? DBPriv = null; + private static string? Pub = null; + internal static RSAParameters PrivateKey + { + get + { + if (Priv == null) + { + Priv = RSA.ExportParameters(true); + Pub = RSA.ToXmlString(false); + using RSACryptoServiceProvider rsa = new(4096); + rsa.FromXmlString(PrivateKeyString); + DBPriv = rsa.ToXmlString(true); + } + return (RSAParameters)Priv; + } + } + + public static string PrivateKeyString + { + get + { + return "2pR655ycDWZuQiPWlcpvloFo2lmcxZJ88nlwNANOri8y+jCrqYvJLLjHW/PHBzDSx/wHEL6GYm/FIPdywgcHzi1a5tuDcdRBrKKqQU/mTW3z2d38H+BJlUCQT/Dai5SXX52p6ZSQBj2Tb4cHZSv3/6vLh1dtJdHYW+RlnadGCKoLJNBF/FQFPBgRkuP69XfAnyM8RTLzzQAcwdmw00pHOdoYV0Ue70MOF3Vv1InnxmB4uOPW6MK45NPH/1u1NkVcp3OZWRAv3xJHfS5CAwtDddnxe4a9f/grvV0lxx4ZCYRLgoe5Lv+0NVIcL+oXdJUI9geyZpfxDpdjdPRc8IA+t+0yyUHTEjyQ3focmFc8fNkmiaxji5sNvG1pWq8Q8MaqgIk1gjRSKJTnWU9N54BMB4pYgRXdVAj/jPS23tgPlcDZNMKvhzIWnzPKEA5CXICPa5DbNYfUbX4DGZlhFv6xkpMMx6jx6WPqIq+XmfaUq6NG6/E/KjbsKzTRk5C4Jfk6doiP5HSdXwP9+yXb1XRus736YoiIABuoCnj8h7OD7+ux31PcCP9PXFayMMxfxZglhw7GhT7MlNb5E9c6ELTqk7exLVxxXG8VL/RpfgyiUzliOjQIM4xyRGBCTXWbr0kRLoW0T9PzGFJzfu2smxSEdmSR9bkC7/i5T3msYa2RiSk=AQAB

5coLKzyBRhzAi8myKb6neZJTxQ2MgQAtXmWCT0xr1xl7wfJ1oQ2q5EQ/FKKD6ejbAyJoJ0deE9dcWUNaxvUmIa9tqHoqxpwr4dBu0IWY+9ZjbS9AmPEzJF0pDqA6NZ5ctxUZ00WzG+swepfjXOPuXqmCX1aV32zJPBN2fo2nrozOdZDqMUB3AeFu4B8zVJKYEXTzVRLnpvY7GdH2xdL/3XvHKwfnrbGv+ScHuNBP1t4oRbNTGLU6eiyK5YbpSL895HrvZYjrg9fkKQDfL0xGQLJOw/agCbwLNlSLhfBSNOfS6B4ehsMurR71Ckpg+kfzy87DssHk6oUrqFD8EcNSqw==

84MfGPF7ivPs4hNj7CcVTh8XgOj25kb4j7X+LXzmNTnBtQ/nO9V2j0NUTg/R5gkwSFEyBIJB9lq/wsuebfS22jmxg7mwgVHFFl7JyqcRaiYY1Dl1HKTKgeCsuY7yP5WQCLfYbQ1G7tcgtcQzlPV86W2pgWsVcxcq8gqbk3k4GbPd30kPmfCPxxtxIhyoQxqPxx/FV12PeN9z+G2A70nPDwtkZ2MiiVj/RttTTPrijd5KjV6zS4jDHeYgfO3NZUviyJ8YvhCKB4ttcjM77BV8HdRgBg4XHO+NwDsMukNvAYaPDEfK42UIhne7CZJ/il8h/4OKxRzfFtAnTx/pQ8Jzew==CnHzrgRzD9/QtMn3SkR7UmBfZG6oO1jptwfAM6CSqlVjNb6ysB5x7SxY/bQhcOl/wxW2TErHMPmyHfCc2Lxd/lv+DRF4jkydBge2cc4Q1Sm6nUTvl8QnAfkmG58W5kcLidrwsJTTfmpjar8qu5c0x6LG5VSHPX+xagSsdzYzMBEAdYGf05tNjY1Uv+VLLQX42ZpKUUypsQIyT4smv3lG3id5NzCFzHRuPlIS3MjDSE4S4JA1L8NVJCaQLbzDL+ZZhuA7r47YvcZ7fY2nl3vNGbXBNNEqFycwD0kqim0RH4yGHrz3wEJxBbeJhe05mUbaAyKj7KU5pZtmD6GWw6vwPw==ZUnZGYr4lGe51J+0JHptRj1wjVJZwJcstLpCq7EUIHeRtzqSODUmR5j97CpwaHrR9oKvh2iW/13n/aKsl5f+pu7wg5YtcN0OWau7y+uKNtj54uyzZeK1ySgnMFfhM8mGS9oMz++B3b7mADVIL2GdP4s5wndESMcOOfdnlwQI7cf/Ne0x7Bo/89XaTRIWezMFMxJoB1sHXoOzvVXvF5lf4yYd8VMu/mpiZJq+H3sL2W7pG7yUX4rXfgxG3zAbC1NxVXm31PcUMucv8xyUhDK7mbzI5DvgKU0LbTYiqSd7eOr7fWQvZD6WOTh8OBMTsf64KYwRoMPNl7OlZigj4udzGQ==Z5mTYcXZwGh4CZLG5w80GULXzmPm8UQadZJf2PHqSDSNYFdsNGvYP/H1qtn+ZHr6SGJRF2Q0E0MqBIcAZ+iZ7IaaQ+pDszXUoqBKruuLbNk4u2ClYBWjx4ziKfPzF68utOm49EN+Zh7sTOQvUlAO7STE1iuUUGZbCvNybSoH1EP8J+snMPdlIK0M2vdE/yKI7jyqD6NGPTIJYyfnvwkRDr1cv88MgNmOEed8lnTrw69Su21WL2Eh8ePSxStTKQpVMXdefpfTx7B7TrFwDSw7P22RjHc6qFktBXlixwYjuHZBv2OwBA6ii1jQzZjT/IqJFT99pmOmg2BchQwOfD5WCQ==DG0sOZZT4YfPv+SFXWRcs1vtvUV381wwd0vZHv6LJBLx3SW+30zfpd0Rab8HsyHJnJii7DGbYTrvzup9HzPCyVbBwrslvhHDnrrBXs+EjSVcRigSc9t5Q3TKtN4WsUsZef70JcqOOj9DmQUTsg2YXp7X23y9wKHX9jgbYNBIMSjoU5z1mvKfOOXjZeGefFhlCTG5ykirHJ342eUe4jw9pWJ/8heEuonU4heJQTY1M417f4NlzeYiwyZDygGJi/7QgC1DFrxOrxS9H2GYW3pHSQrCvvOPkBgTSy42/hrOG0DmrBKRlW7wbKbw4aEiLpIPKDGS07X9Udzgc3SLA28/DB9AdX6qZSKEmf33LwK47wLEZkiLbU/IO/kb0MpSpl77TrMDrl1J+3Kgcmz7q9eF83W9519/uZgZpOSO/a/97oMKZSfLeyASVP7K1EcqK23K/wjvaP3nvSMGsiQmqCn4jItgfH5N/tcnkNz2DKz3BPcfgAClVH1MFsjDlBqYSHXIqwAFQ8SDBixrPoLoJDkygFoNIrrXqbArVYqXOAgODo2t0foENa/msPJyZ6m6UPi3bMY3gji5qjaSEcvbBmhlSVyHmumgOJ5Rl+L5dS5jAyPgybcVYKab71XQf5sm1UhA8iFWqYgyglmR/b6F9UTFi1QwZA6rgvELFv7DvqjLbmM=
"; + } + } + + public static string PublicKey + { + get + { + if (Pub == null) + { + Priv = RSA.ExportParameters(true); + Pub = RSA.ToXmlString(false); + using RSACryptoServiceProvider rsa = new(4096); + rsa.FromXmlString(PrivateKeyString); + DBPriv = rsa.ToXmlString(true); + } + return Pub; + } + } + } + + public static readonly UnicodeEncoding _encoder = new(); + + public static byte[] Decrypt(byte[] data, bool multithread = false) + { + return Decrypt(data, Keys.PrivateKey, multithread); + } + + public static byte[] Decrypt(byte[] data, string Key, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(Key); + return Decrypt(data, rsa.ExportParameters(true), multithread); + } + + public static byte[] Decrypt(byte[] EncryptedText, RSAParameters Key, bool multithread = false) + { + if (EncryptedText is null) throw new ArgumentNullException(nameof(EncryptedText)); + using RSACryptoServiceProvider rsa = new(); + rsa.ImportParameters(Key); + int size = rsa.KeySize / 8; + double x = EncryptedText.Length / (double)size; + int bbb = int.Parse(x.ToString().Split('.')[0]); + if (x.ToString().Contains('.')) bbb++; + byte[]? datasplitout = Array.Empty(); + if (multithread) + { + byte[][]? decccc = Array.Empty(); + Array.Resize(ref decccc, bbb); + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 25) * 2.0)); + if (num == 0) num = 1; + Parallel.For(0, bbb, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + decccc[i] = rsa.Decrypt(EncryptedText.Skip(i * size).Take(size).ToArray(), false); + }); + foreach (byte[] data in decccc) + { + datasplitout = Combine(datasplitout, data); + } + } + else + { + for (int i = 0; i < bbb; i++) + { + datasplitout = Combine(datasplitout, rsa.Decrypt(EncryptedText.Skip(i * size).Take(size).ToArray(), false)); + } + } + return datasplitout; + + /* + + using (RSACryptoServiceProvider? rsa = new()) + { + rsa.ImportParameters(Key); + double x = ((double)EncryptedText.Length / (double)512); + int bbb = int.Parse(x.ToString().Split('.')[0]); + if (x.ToString().Contains('.')) bbb++; + byte[][] datasplit = Array.Empty(); + byte[] datasplitout = Array.Empty(); + Array.Resize(ref datasplit, bbb); + for (int i = 0; i < bbb; i++) + { + byte[] fff = EncryptedText.Skip(i * 512).Take(512).ToArray(); + datasplit[i] = fff; + datasplitout = Combine(datasplitout, rsa.Decrypt(datasplit[i], false)); + } + return datasplitout; + }*/ + } + + public static byte[] Encrypt(string Text, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(Keys.PublicKey); + return Encrypt(_encoder.GetBytes(Text), rsa.ExportParameters(false), multithread); + } + + public static byte[] Encrypt(string Text, string key, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(key); + return Encrypt(_encoder.GetBytes(Text), rsa.ExportParameters(false), multithread); + } + + public static byte[] Encrypt(byte[] data, string key, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(key); + return Encrypt(data, rsa.ExportParameters(false), multithread); + } + + private static byte[] Combine(byte[] first, byte[] second) + { + byte[] bytes = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, bytes, 0, first.Length); + Buffer.BlockCopy(second, 0, bytes, first.Length, second.Length); + return bytes; + } + + public static byte[] Encrypt(string Text, RSAParameters Key, bool multithread = false) + { + return Encrypt(_encoder.GetBytes(Text), Key, multithread); + } + + public static byte[] Encrypt(byte[] data, RSAParameters Key, bool multithread = false) + { + using RSACryptoServiceProvider rsa = new(); + rsa.ImportParameters(Key); + int size = rsa.KeySize / 8; + double x = data.Length / (double)size; + int bbb = int.Parse(x.ToString().Split('.')[0]); + if (x.ToString().Contains('.')) bbb++; + byte[]? datasplitout = Array.Empty(); + if (multithread) + { + byte[][]? decccc = Array.Empty(); + Array.Resize(ref decccc, bbb); + int num = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 25) * 2.0)); + if (num == 0) num = 1; + Parallel.For(0, bbb, new ParallelOptions() + { + MaxDegreeOfParallelism = num + }, i => + { + decccc[i] = rsa.Encrypt(data.Skip(i * size).Take(size).ToArray(), false); + }); + foreach (byte[] dataa in decccc) + { + datasplitout = Combine(datasplitout, dataa); + } + } + else + { + for (int i = 0; i < bbb; i++) + { + datasplitout = Combine(datasplitout, rsa.Encrypt(data.Skip(i * size).Take(size).ToArray(), false)); + } + } + return datasplitout; + } + } +} \ No newline at end of file diff --git a/LuskiServer/Classes/PluginWarningAttribute.cs b/LuskiServer/Classes/PluginWarningAttribute.cs new file mode 100644 index 0000000..3f501a8 --- /dev/null +++ b/LuskiServer/Classes/PluginWarningAttribute.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; + +namespace LuskiServer.Classes; + +[AttributeUsage(AttributeTargets.Field)] +public class PluginWarningAttribute : Attribute +{ + public PluginWarningAttribute([DisallowNull]string Message) + { + _mess = Message; + } + + private string _mess; + + public string Message + { + get + { + return _mess; + } + set + { + _mess = value; + } + } +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Categories.cs b/LuskiServer/Classes/TableDef/Categories.cs new file mode 100644 index 0000000..ad80f30 --- /dev/null +++ b/LuskiServer/Classes/TableDef/Categories.cs @@ -0,0 +1,14 @@ +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Categories +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn Name { get; } = new("name"); + public static TableColumn Description { get; } = new("description"); + public static TableColumn InnerCategories { get; } = new("inner_categories") { DefaultValue = Array.Empty() }; + public static TableColumn Channels { get; } = new("channels") { DefaultValue = Array.Empty() }; + public static TableColumn RoleOverides { get; } = new("role_overides") { DefaultValue = Array.Empty() }; + public static TableColumn UserOverides { get; } = new("member_overides") { DefaultValue = Array.Empty() }; +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Channels.cs b/LuskiServer/Classes/TableDef/Channels.cs new file mode 100644 index 0000000..c480695 --- /dev/null +++ b/LuskiServer/Classes/TableDef/Channels.cs @@ -0,0 +1,15 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Channels +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn Type { get; } = new("type"); + public static TableColumn Name { get; } = new("name"); + public static TableColumn Description { get; } = new("description"); + public static TableColumn Key { get; } = new("key"); + public static TableColumn RoleOverides { get; } = new("role_overides"); + public static TableColumn UserOverides { get; } = new("member_overides"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Files.cs b/LuskiServer/Classes/TableDef/Files.cs new file mode 100644 index 0000000..2475edb --- /dev/null +++ b/LuskiServer/Classes/TableDef/Files.cs @@ -0,0 +1,12 @@ +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Files +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn Size { get; } = new("size"); + public static TableColumn Name { get; } = new("name"); + public static TableColumn Hash { get; } = new("hash"); + public static TableColumn Data { get; } = new("data"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Logs.cs b/LuskiServer/Classes/TableDef/Logs.cs new file mode 100644 index 0000000..b88a695 --- /dev/null +++ b/LuskiServer/Classes/TableDef/Logs.cs @@ -0,0 +1,11 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Logs +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn Type { get; } = new("type"); + public static TableColumn Message { get; } = new("message"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Messages.cs b/LuskiServer/Classes/TableDef/Messages.cs new file mode 100644 index 0000000..59f761c --- /dev/null +++ b/LuskiServer/Classes/TableDef/Messages.cs @@ -0,0 +1,13 @@ +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Messages +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn ChannelID { get; } = new("channel_id"); + public static TableColumn AuthorID { get; } = new("author_id"); + public static TableColumn TimeStamp { get; } = new("ts"); + public static TableColumn Context { get; } = new("context"); + public static TableColumn Files { get; } = new("files"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Roles.cs b/LuskiServer/Classes/TableDef/Roles.cs new file mode 100644 index 0000000..7483641 --- /dev/null +++ b/LuskiServer/Classes/TableDef/Roles.cs @@ -0,0 +1,13 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class Roles +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn Name { get; } = new("name"); + public static TableColumn Color { get; } = new("color"); + public static TableColumn Description { get; } = new("description"); + public static TableColumn ServerPermissions { get; } = new("server_perms"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/ServerRoleOverides.cs b/LuskiServer/Classes/TableDef/ServerRoleOverides.cs new file mode 100644 index 0000000..1b57c8a --- /dev/null +++ b/LuskiServer/Classes/TableDef/ServerRoleOverides.cs @@ -0,0 +1,11 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class ServerRoleOverides +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn RoleID { get; } = new("role_id"); + public static TableColumn Overides { get; } = new("overides"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/SessionTokens.cs b/LuskiServer/Classes/TableDef/SessionTokens.cs new file mode 100644 index 0000000..7431ff9 --- /dev/null +++ b/LuskiServer/Classes/TableDef/SessionTokens.cs @@ -0,0 +1,12 @@ +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class SessionTokens +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn AccountID { get; } = new("account_id"); + public static TableColumn Token { get; } = new("token"); + public static TableColumn AddressFilter { get; } = new("address_filter"); + public static TableColumn TimeFilter { get; } = new("date_filter"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/UserRoleOverides.cs b/LuskiServer/Classes/TableDef/UserRoleOverides.cs new file mode 100644 index 0000000..dd28953 --- /dev/null +++ b/LuskiServer/Classes/TableDef/UserRoleOverides.cs @@ -0,0 +1,12 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public static class UserRoleOverides +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn UserID { get; } = new("user_id"); + public static TableColumn ParentOverideID { get; } = new("parent_overide_id"); + public static TableColumn Overides { get; } = new("overides"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/TableDef/Users.cs b/LuskiServer/Classes/TableDef/Users.cs new file mode 100644 index 0000000..3b3e111 --- /dev/null +++ b/LuskiServer/Classes/TableDef/Users.cs @@ -0,0 +1,24 @@ +using LuskiServer.Enums; +using ServerDatabase; + +namespace LuskiServer.Classes.TableDef; + +public class Users +{ + public static TableColumn ID { get; } = new("id", true); + public static TableColumn DisplayName { get; } = new("displayname"); + public static TableColumn SelectedChannel { get; } = new("selected_channel"); + public static TableColumn Status { get; } = new("status"); + public static TableColumn PictureType { get; } = new("picture_type"); + public static TableColumn Picture { get; } = new("picture"); + public static TableColumn Roles { get; } = new("roles"); + public static TableColumn Username { get; } = new("username"); + public static TableColumn Password { get; } = new("password"); + public static TableColumn Salt { get; } = new("salt"); + public static TableColumn LoginToken { get; } = new("login_token"); + public static TableColumn SessionKey { get; } = new("session_key"); + public static TableColumn WSSTCP { get; } = new("wsstcp"); + public static TableColumn Token { get; } = new("token"); + public static TableColumn OfflineData { get; } = new("offline_data"); + public static TableColumn OffileKey { get; } = new("offline_key"); +} \ No newline at end of file diff --git a/LuskiServer/Classes/Tables.cs b/LuskiServer/Classes/Tables.cs new file mode 100644 index 0000000..f5bb2ac --- /dev/null +++ b/LuskiServer/Classes/Tables.cs @@ -0,0 +1,17 @@ +using ServerDatabase; + +namespace LuskiServer.Classes; + +public static class Tables +{ + public static Table Users { get; } = new Table("users", null!); + public static Table Roles { get; } = new Table("roles", null!); + public static Table Logs { get; } = new Table("logs", null!); + public static Table Files { get; } = new Table("files", null!); + public static Table Categories { get; } = new Table("categories", null!); + public static Table Channels { get; } = new Table("channels", null!); + public static Table Messages { get; } = new Table("messages", null!); + public static Table ServerRoleOverides { get; } = new Table("role_overides", null!); + public static Table UserRoleOverides { get; } = new Table("user_overides", null!); + public static Table SessionTokens { get; } = new Table("session_tokens", null!); +} \ No newline at end of file diff --git a/LuskiServer/ConfigureSwaggerOptions.cs b/LuskiServer/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..56d5252 --- /dev/null +++ b/LuskiServer/ConfigureSwaggerOptions.cs @@ -0,0 +1,90 @@ +using Asp.Versioning; + +namespace LuskiServer; + +using Asp.Versioning.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text; + +/// +/// Configures the Swagger generation options. +/// +/// This allows API versioning to define a Swagger document per API version after the +/// service has been resolved from the service container. +public class ConfigureSwaggerOptions : IConfigureOptions +{ + private readonly IApiVersionDescriptionProvider provider; + + /// + /// Initializes a new instance of the class. + /// + /// The provider used to generate Swagger documents. + public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider; + + /// + public void Configure(SwaggerGenOptions options) + { + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach (ApiVersionDescription description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + StringBuilder text = new StringBuilder("A simple way to view nice information to understand API v" + description.ApiVersion + "."); + OpenApiInfo info = new OpenApiInfo() + { + Title = "Luski Server API", + Version = description.ApiVersion.ToString(), + //Contact = new OpenApiContact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, + //License = new OpenApiLicense() { Name = "MIT", Url = new Uri( "https://opensource.org/licenses/MIT" ) } + }; + + if (description.IsDeprecated) + { + text.Append(" This API version has been deprecated."); + } + + if (description.SunsetPolicy is { } policy) + { + if (policy.Date.HasValue) + { + text.Append(" The API will be sunset on ") + .Append(policy.Date.Value.Date.ToShortDateString()) + .Append('.'); + } + + if (policy.HasLinks) + { + text.AppendLine(); + + for (int i = 0; i < policy.Links.Count; i++) + { + LinkHeaderValue link = policy.Links[i]; + + if (link.Type == "text/html") + { + text.AppendLine(); + + if (link.Title.HasValue) + { + text.Append(link.Title.Value).Append(": "); + } + + text.Append(link.LinkTarget.OriginalString); + } + } + } + } + + info.Description = text.ToString(); + + return info; + } +} \ No newline at end of file diff --git a/LuskiServer/Controllers/v1/CreateAccountController.cs b/LuskiServer/Controllers/v1/CreateAccountController.cs new file mode 100644 index 0000000..2a6c32b --- /dev/null +++ b/LuskiServer/Controllers/v1/CreateAccountController.cs @@ -0,0 +1,172 @@ +using System.Net.Mime; +using System.Security.Cryptography; +using System.Text; +using Asp.Versioning; +using LuskiServer.Classes; +using LuskiServer.Classes.TableDef; +using LuskiServer.Enums; +using Microsoft.AspNetCore.Mvc; + +namespace LuskiServer.Controllers.v1; + +[ApiVersion(1)] +[ApiController] +public class CreateAccountController : ControllerBase +{ + /// + /// Make a post request to this endpoint for registering an account on the server. + /// + /// Encrypted email for account + /// Hashed Password for account + /// Plain text Username + /// Plain text RSA key for server to respond with + /// Raw Picture data for profile + /// + [HttpPost] + [DisableRequestSizeLimit] + [Consumes(MediaTypeNames.Image.Jpeg, MediaTypeNames.Image.Gif, MediaTypeNames.Image.Tiff, "image/png")] + [Produces(MediaTypeNames.Application.Json)] + [ProducesResponseType(typeof(Login), StatusCodes.Status201Created)] + [ProducesResponseType(typeof(Login), StatusCodes.Status403Forbidden)] + [Route(Luski.Info.Routes.Default.Base)] + public IActionResult Post([FromBody]byte[] Body, [FromHeader(Name = "username")]string? UsernameRaw, [FromHeader(Name = "password")]string? PasRaw, [FromHeader(Name = "displayname")]string? DisplayName, [FromHeader(Name = "key")]string? KeyRaw) + { + try + { + PictureType pfp; + byte[] PasBytes, Username; + byte[] salt = new byte[100]; + try + { + using (RandomNumberGenerator provider = RandomNumberGenerator.Create()) + { + provider.GetBytes(salt); + } + try + { + PasBytes = Luski.Encryption.RemotePasswordEncrypt(Luski.Encryption._encoder.GetString(Luski.Encryption.Decrypt(Convert.FromBase64String(PasRaw))), salt); + Username = Luski.Encryption.Decrypt(Convert.FromBase64String(UsernameRaw)); + + try + { + byte[] g = Luski.Encryption.Encrypt("Test data to send to client", KeyRaw); + } + catch + { + return this.ShowError(ErrorCode.InvalidHeader, "The public keys you gave cant be used to encrypt data"); + } + } + catch + { + return this.ShowError(ErrorCode.InvalidHeader, "Make sure your login is encrypted with the server provided public key"); + } + } + catch (Exception ex) + { + if (ex.Message.Contains("not found")) return this.ShowError(ErrorCode.MissingHeader, "Missing login infermation"); + return this.ShowError(ex); + } + + if (CheckUsername(Username)) + { + int num = new Random().Next(1000, 1000000000); + int num2 = new Random().Next(1000, 1000000000); + Luski.Snowflake id = Luski.Snowflake.GenerateSnowflake(WorkerId.CreateAccount); + byte[] ID = Encoding.UTF8.GetBytes(id.ID.ToString()); + byte[] Timestamp = Encoding.UTF8.GetBytes(DateTime.UtcNow.ToString()); + byte[] Number = Encoding.UTF8.GetBytes(num.ToString()); + byte[] Number2 = Encoding.UTF8.GetBytes(num2.ToString()); + string Token = $"{Convert.ToBase64String(ID)}.{Convert.ToBase64String(Timestamp)}.{Convert.ToBase64String(Number)}.{Convert.ToBase64String(Number2)}"; + + pfp = GetProfilePictureType(Encoding.UTF8.GetString(Body).ToUpper()); + + Tables.Users.Insert( + Users.ID.CreateParameter(id.ID), + Users.DisplayName.CreateParameter(DisplayName!.Replace("'", "\'")), + Users.SelectedChannel.CreateParameter(0), //TODO set to default + Users.Status.CreateParameter(Status.Offline), + Users.PictureType.CreateParameter(pfp), + Users.Picture.CreateParameter(Body), + Users.Roles.CreateParameter(Array.Empty()), + Users.Username.CreateParameter(Username), + Users.Password.CreateParameter(PasBytes), + Users.Salt.CreateParameter(salt), + Users.LoginToken.CreateParameter(Token), + Users.SessionKey.CreateParameter(KeyRaw), + Users.WSSTCP.CreateParameter(string.Empty), + Users.Token.CreateParameter(string.Empty), + Users.OfflineData.CreateParameter(Array.Empty()), + Users.OffileKey.CreateParameter(string.Empty)); + Thread t = new(o => RegToken((string?)o)); + t.Start(Token); + return StatusCode(201, new Login() + { + login_token = Token + }); + } + return this.ShowError(ErrorCode.Forbidden, "That email is already being used"); + } + catch (Exception ex) + { + return this.ShowError(ex); + } + } + + private static PictureType GetProfilePictureType(string headerCode) + { + if (headerCode.StartsWith("FFD8FFE0")) + { + return PictureType.jpeg; + } + else if (headerCode.StartsWith("49492A")) + { + return PictureType.tif; + } + else if (headerCode.StartsWith("424D")) + { + return PictureType.bmp; + } + else if (headerCode.StartsWith("GIF")) + { + return PictureType.gif; + } + else if (headerCode.Remove(0, 1).StartsWith("PNG")) + { + return PictureType.png; + } + else + { + return PictureType.png; + } + } + + /// + /// Cheaks to see Username is free + /// + /// + /// + /// true = Good + /// false = Username Taken + /// + private static bool CheckUsername(byte[] Username) + { + if (Tables.Users.TryRead(Users.Username, out _, Users.Username.CreateParameter(Username))) return false; + return true; + } + + private static void RegToken(string? token) + { + try + { + if (token == null) return; + long id = long.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(token.Split('.')[0]))); + Tables.Users.Update(Users.ID, id, Users.LoginToken.CreateParameter(token)); + Thread.Sleep(30000); + Tables.Users.Update(Users.ID, id, Users.LoginToken.CreateParameter(string.Empty)); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } +} \ No newline at end of file diff --git a/LuskiServer/Controllers/v1/KeysController.cs b/LuskiServer/Controllers/v1/KeysController.cs new file mode 100644 index 0000000..4c8333a --- /dev/null +++ b/LuskiServer/Controllers/v1/KeysController.cs @@ -0,0 +1,67 @@ +using System.Net.Mime; +using System.Text; +using Asp.Versioning; +using LuskiServer.Classes; +using LuskiServer.Classes.TableDef; +using LuskiServer.Enums; +using Microsoft.AspNetCore.Mvc; + +namespace LuskiServer.Controllers.v1; + +[ApiVersion(1)] +[ApiController] +public class KeysController : ControllerBase +{ + /// + /// Return a public RSA key to encrypt information directed for the server. + /// + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [Produces(MediaTypeNames.Application.Xml)] + [Route(Luski.Info.Routes.Default.Base + "/PublicKey")] + public IActionResult PublicKey() + { + return File(Encoding.UTF8.GetBytes(Luski.Encryption.Keys.PublicKey), "application/xml"); + } + + /// + /// Make a post request to this URL to set you offline data key. + /// + /// Your Luski token for the server + /// The key you want to set for when you go offline + /// + [HttpPost] + [Consumes(MediaTypeNames.Application.Xml)] + [Produces(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(typeof(HTTPResponse), StatusCodes.Status403Forbidden)] + [Route(Luski.Info.Routes.Default.Base + "/SetOfflineKey")] + public IActionResult SetOfflineKey([FromBody]byte[]? keyy, [FromHeader(Name = "token")]string? token) + { + try + { + string key = ""; + if (!this.CanTokenRequest(out long ID, out IActionResult? toc) && toc != null) return toc; + string[]? data = Tables.Users.Read(Users.OfflineData, Users.ID.CreateParameter(ID)); + if (data is not null) return this.ShowError(ErrorCode.Forbidden, "you cant change your key untill you download your data"); + + try + { + byte[] g = Luski.Encryption.Encrypt("Test data to send to client", key); + } + catch + { + + return this.ShowError(ErrorCode.InvalidPostData, "The key you sent the server appears to be incorect"); + } + Tables.Users.Update(Users.ID, ID, + Users.OffileKey.CreateParameter(key)); + return StatusCode(202); + } + catch (Exception ex) + { + return this.ShowError(ex); + } + } +} \ No newline at end of file diff --git a/LuskiServer/Controllers/v1/SocketUserProfileController.cs b/LuskiServer/Controllers/v1/SocketUserProfileController.cs new file mode 100644 index 0000000..01e0834 --- /dev/null +++ b/LuskiServer/Controllers/v1/SocketUserProfileController.cs @@ -0,0 +1,163 @@ +using System.Net.Mime; +using System.Text; +using Asp.Versioning; +using LuskiServer.Classes; +using LuskiServer.Classes.ActionFilters; +using LuskiServer.Classes.TableDef; +using LuskiServer.Enums; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace LuskiServer.Controllers.v1; + +[ApiVersion(1)] +[ApiController] +public class SocketUserProfileController : ControllerBase +{ + /// + /// Returns the Status of a user. + /// + /// The Id of the user + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HTTPResponse), StatusCodes.Status403Forbidden, MediaTypeNames.Application.Json)] + [Produces(MediaTypeNames.Text.Plain, MediaTypeNames.Application.Json)] + [Route(Luski.Info.Routes.Default.Base + "/Status/{id:long}")] + public Status Status(long id) + { + Status status = Tables.Users.Read(Users.Status, Users.ID.CreateParameter(id)); + return status; + } + + /// + /// Returns the Avatar for the user. + /// + /// The ID of the requested user + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HTTPResponse), StatusCodes.Status403Forbidden)] + [Produces(MediaTypeNames.Image.Jpeg, MediaTypeNames.Image.Gif, MediaTypeNames.Image.Tiff, "image/png", MediaTypeNames.Application.Json)] + [Route(Luski.Info.Routes.Default.Base + "/Avatar/{id:long}")] + public IActionResult? Avatar(long id) + { + if (Tables.Users.TryRead(Users.Picture, out byte[]? image, Users.ID.CreateParameter(id))) + { + return Tables.Users.Read(Users.PictureType, Users.ID.CreateParameter(id)) switch + { + PictureType.png => File(image, "image/png"), + PictureType.jpeg => File(image, "image/jpeg"), + PictureType.bmp => File(image, "image/bmp"), + PictureType.gif => File(image, "image/gif"), + PictureType.ico => File(image, "image/vnd.microsoft.icon"), + PictureType.svg => File(image, "image/svg+xml"), + PictureType.tif => File(image, "image/tiff"), + PictureType.webp => File(image, "image/webp"), + //should never happen + _ => File(image, "image/png"), + }; + } + else + { + return StatusCode(403, new HTTPResponse() + { + error = ErrorCode.Forbidden, + error_message = "the user you have givven does not exist" + }); + //return File(Encoding.UTF8.GetBytes(obj.ToString()), "application/json"); + } + } + + public class StatusUpdate + { + [BindRequired] + public Status status { get; set; } + } + + /// + /// Make a post to this endpoint to change the status of you account. + /// + /// Infomation required for status update. + /// The Token for your account. + /// + [HttpPost] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(typeof(HTTPResponse), StatusCodes.Status403Forbidden)] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(typeof(StatusUpdate), MediaTypeNames.Application.Json)] + [Route(Luski.Info.Routes.Default.Base + "/Status")] + [TokenFilter] + public IActionResult Status([FromBody]StatusUpdate SentData) + { + try + { + if (!this.CanTokenRequest(out long ID, out IActionResult? toc) && toc != null) return toc; + + //dynamic? SentData = Newtonsoft.Json.JsonConvert.DeserializeObject(content); + Status NewStatus = SentData.status; + Status OldStatus = Tables.Users.Read(Users.Status, Users.ID.CreateParameter(ID)); + if (OldStatus != NewStatus && (int)NewStatus < (int)Enums.Status.MAX && (int)NewStatus >= 0) + { + // JObject @out = new() + // { + // { "id", ID }, + // { "before", (int)OldStatus }, + // { "after", (int)NewStatus }, + // { "type", (int)DataType.Status_Update } + // }; + Tables.Users.Update(Users.ID, ID, Users.Status.CreateParameter(NewStatus)); + if (NewStatus == Enums.Status.Invisible) NewStatus = Enums.Status.Offline; + // WSS.SendData(SendType.All, @out); + } + + return StatusCode(202); + } + catch (Exception ex) + { + return this.ShowError(ex); + } + } +/* + [HttpPost] + [Route(Luski.Info.Route.Version1.Action)] + public async Task Activity() + { + try + { + if (!this.CanTokenRequest(out long token_id, out HTTPResponse? toc) && toc != null) return StatusCode(403, toc); + string content; + + using (StreamReader reader = new(Request.Body)) + { + content = reader.ReadToEnd(); + } + if (string.IsNullOrEmpty(content)) return StatusCode(403, this.ShowError(ErrorCode.MissingPostData)); + dynamic? SentData = Newtonsoft.Json.JsonConvert.DeserializeObject(content); + if (SentData == null) return this.ShowError(ErrorCode.MissingPostData); + if (string.IsNullOrEmpty((string)SentData.activity)) return this.ShowError(ErrorCode.MissingPostData); + string NewActivity = (string)SentData.activity; + string OldActivity = Luski.Database.Read("users", "activity", Luski.Database.CreateParameter("id", token_id)); + if (OldActivity != NewActivity) + { + JObject @out = new() + { + { "id", token_id }, + { "before", OldActivity }, + { "after", NewActivity } + }; + Luski.Database.Update("users", "id", token_id, Luski.Database.CreateParameter("activity", NewActivity)); + WSS.SendData(SendType.All, Program.Output(DataType.Status_Update, @out)); + } + + Response.Clear(); + Response.StatusCode = StatusCodes.Status200OK; + await Response.StartAsync(); + return null; + } + catch (Exception ex) + { + return this.ShowError(ErrorCode.ServerError, ex); + } + }*/ +} \ No newline at end of file diff --git a/LuskiServer/Enums/ChannelType.cs b/LuskiServer/Enums/ChannelType.cs new file mode 100644 index 0000000..f6587ad --- /dev/null +++ b/LuskiServer/Enums/ChannelType.cs @@ -0,0 +1,6 @@ +namespace LuskiServer.Enums; + +public enum ChannelType : short +{ + +} \ No newline at end of file diff --git a/LuskiServer/Enums/CommandServiceType.cs b/LuskiServer/Enums/CommandServiceType.cs new file mode 100644 index 0000000..c2c8d77 --- /dev/null +++ b/LuskiServer/Enums/CommandServiceType.cs @@ -0,0 +1,9 @@ +using Microsoft.VisualBasic.CompilerServices; + +namespace LuskiServer.Enums; + +public enum CommandServiceType : short +{ + Server, + Plugin +} \ No newline at end of file diff --git a/LuskiServer/Enums/ErrorCode.cs b/LuskiServer/Enums/ErrorCode.cs new file mode 100644 index 0000000..b1a5e3b --- /dev/null +++ b/LuskiServer/Enums/ErrorCode.cs @@ -0,0 +1,14 @@ +namespace LuskiServer.Enums; + +public enum ErrorCode +{ + MissingToken, + InvalidToken, + MissingPostData, + InvalidPostData, + Forbidden, + ServerError, + MissingHeader, + InvalidHeader, + InvalidURL +} \ No newline at end of file diff --git a/LuskiServer/Enums/LogType.cs b/LuskiServer/Enums/LogType.cs new file mode 100644 index 0000000..6f8529b --- /dev/null +++ b/LuskiServer/Enums/LogType.cs @@ -0,0 +1,6 @@ +namespace LuskiServer.Enums; + +public enum LogType : short +{ + Error +} \ No newline at end of file diff --git a/LuskiServer/Enums/PictureType.cs b/LuskiServer/Enums/PictureType.cs new file mode 100644 index 0000000..93b65be --- /dev/null +++ b/LuskiServer/Enums/PictureType.cs @@ -0,0 +1,13 @@ +namespace LuskiServer.Enums; + +public enum PictureType : short +{ + png, + jpeg, + bmp, + gif, + ico, + svg, + tif, + webp +} \ No newline at end of file diff --git a/LuskiServer/Enums/PluginPerms.cs b/LuskiServer/Enums/PluginPerms.cs new file mode 100644 index 0000000..32014ad --- /dev/null +++ b/LuskiServer/Enums/PluginPerms.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; +using LuskiServer.Classes; + +namespace LuskiServer.Enums; + +[Flags] +public enum PluginPerms : long +{ + CustomeServerRoleSet = 0b_0000_0001, + AddCommands = 0b_0000_0010, + [PluginWarning("This permission is considred dangerous. The plugin will have private keys to some channels.")] + ReadMessages = 0b_0000_0100, + SendMessages = 0b_0000_1000, + FalseUser = 0b_0001_0000 +} \ No newline at end of file diff --git a/LuskiServer/Enums/ServerPermissions.cs b/LuskiServer/Enums/ServerPermissions.cs new file mode 100644 index 0000000..3e71996 --- /dev/null +++ b/LuskiServer/Enums/ServerPermissions.cs @@ -0,0 +1,29 @@ +namespace LuskiServer.Enums; + +public enum ServerPermissions : long +{ + ViewChannels, + ManageChannels, + ViewCategories, + ManageCategories, + ManageRoles, + ViewLogs, + ManageServer, + Invite, + Nickname, + ManageNacknames, + Kick, + Ban, + SendMessages, + SendFiles, + ChannelAndServerPings, + PingSomeone, + ManageMessages, + ReadMessageHistory, + UseServerCommands, + JoinVoice, + SpeakInVoice, + MuteMembers, + DeafenMembers, + MoveMembers +} \ No newline at end of file diff --git a/LuskiServer/Enums/Status.cs b/LuskiServer/Enums/Status.cs new file mode 100644 index 0000000..04a1172 --- /dev/null +++ b/LuskiServer/Enums/Status.cs @@ -0,0 +1,11 @@ +namespace LuskiServer.Enums; + +public enum Status : short +{ + Offline, + Online, + Idle, + DoNotDisturb, + Invisible, + MAX +} \ No newline at end of file diff --git a/LuskiServer/Enums/WorkerId.cs b/LuskiServer/Enums/WorkerId.cs new file mode 100644 index 0000000..7dba0f1 --- /dev/null +++ b/LuskiServer/Enums/WorkerId.cs @@ -0,0 +1,14 @@ +namespace LuskiServer.Enums; + +public enum WorkerId +{ + Log, + CreateAccount, + Login, + SocketUser, + SocketMessage, + SocketBulkMessage, + SocketChannel, + SocketUserProfile, + SocketUserImage +} \ No newline at end of file diff --git a/LuskiServer/Interfaces/ICommand.cs b/LuskiServer/Interfaces/ICommand.cs new file mode 100644 index 0000000..102b8a4 --- /dev/null +++ b/LuskiServer/Interfaces/ICommand.cs @@ -0,0 +1,11 @@ +using LuskiServer.Enums; + +namespace LuskiServer.Interfaces; + +public interface ICommand +{ + public string Name { get; set; } + public long ServiceID { get; set; } + public CommandServiceType Type { get; set; } + public string ProcessCommand(); +} \ No newline at end of file diff --git a/LuskiServer/Interfaces/IPlugin.cs b/LuskiServer/Interfaces/IPlugin.cs new file mode 100644 index 0000000..ee702a8 --- /dev/null +++ b/LuskiServer/Interfaces/IPlugin.cs @@ -0,0 +1,16 @@ +using LuskiServer.Enums; + +namespace LuskiServer.Interfaces; + +public interface IPlugin +{ + public string Name { get; } + public string Version { get; } + public string Author { get; } + public string Description { get; } + + public PluginPerms GetRequiredPerms(); + public PluginPerms GetOptionalPerms(); + public void Start(PluginPerms perms); + public void Stop(); +} \ No newline at end of file diff --git a/LuskiServer/LuskiServer.csproj b/LuskiServer/LuskiServer.csproj new file mode 100644 index 0000000..b8d3620 --- /dev/null +++ b/LuskiServer/LuskiServer.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + enable + enable + + + + bin\Debug\net7.0\LuskiServer.xml + 1701;1702;IL2121;1591 + ;NU1605;SYSLIB0011;CS8625;CS8714;SYSLIB0011 + + + + bin\Release\net7.0\LuskiServer.xml + ;NU1605;SYSLIB0011;CS8625;CS8714 + + + + + + + + + + + + diff --git a/LuskiServer/Program.cs b/LuskiServer/Program.cs new file mode 100644 index 0000000..c80bd94 --- /dev/null +++ b/LuskiServer/Program.cs @@ -0,0 +1,206 @@ +using System.Reflection; +using Asp.Versioning.ApiExplorer; +using LuskiServer; +using LuskiServer.Classes; +using LuskiServer.Classes.TableDef; +using LuskiServer.Enums; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ServerDatabase; +using Swashbuckle.AspNetCore.SwaggerGen; + +[assembly: Microsoft.AspNetCore.Mvc.ApiController] + +Luski.Config = Luski.GetSettings("/etc/luskiserver/app.json", AppConfigContext.Default.AppConfig, true); +Luski.Database = new Database(Luski.Config.Address, + Luski.Config.Database, + Luski.Config.Username, + Luski.Config.Password, + Luski.Config.CustomeName); + +try +{ + Luski.Database.ExecuteNonQuery($"CREATE DATABASE {Luski.Config.Database} WITH OWNER = {Luski.Config.Username} ENCODING = 'UTF8' CONNECTION LIMIT = -1 IS_TEMPLATE = False;"); + Console.WriteLine("Database Created"); +} +catch (Exception e) +{ + Console.WriteLine("Database Found"); +} + +foreach (PropertyInfo prop in typeof(Tables).GetProperties()) +{ + Table table = (Table)prop.GetValue(null)!; + table.DatabaseHandler = Luski.Database; + Type t = Type.GetType($"LuskiServer.Classes.TableDef.{prop.Name}")!; + foreach(PropertyInfo prop2 in t.GetProperties()) + { + table.WithColumn((ITableColumn)prop2.GetValue(null)!); + } + Luski.Database.Tables.Add(table); +} + +Luski.Database.RegisterTables(); + +if (!Luski.Database.VersionsTable.TryRead(Luski.Database.VersionsTable.ID, out _, + Luski.Database.VersionsTable.ID.CreateParameter(0))) +{ + Luski.Database.VersionsTable.Insert(Luski.Database.VersionsTable.ID.CreateParameter(0)); +} + +if (!Tables.Roles.TryRead(Roles.ID, out _, Roles.ID.CreateParameter(0))) +{ + Tables.Roles.Insert( + Roles.ID.CreateParameter(0), + Roles.Name.CreateParameter("server"), + Roles.Color.CreateParameter("5,5,5,5"), + Roles.Description.CreateParameter("The default role for the server. Everybody will have this role."), + Roles.ServerPermissions.CreateParameter(new[] + { + ServerPermissions.ViewChannels, + ServerPermissions.ViewCategories, + ServerPermissions.Nickname, + ServerPermissions.SendMessages, + ServerPermissions.SendFiles, + ServerPermissions.ChannelAndServerPings, + ServerPermissions.PingSomeone, + ServerPermissions.ReadMessageHistory, + ServerPermissions.UseServerCommands, + ServerPermissions.JoinVoice, + ServerPermissions.SpeakInVoice + }) + ); +} + +if (!Tables.ServerRoleOverides.TryRead(ServerRoleOverides.ID, out _, Categories.ID.CreateParameter(0))) +{ + Tables.ServerRoleOverides.Insert( + ServerRoleOverides.ID.CreateParameter(0), + ServerRoleOverides.RoleID.CreateParameter(0), + ServerRoleOverides.Overides.CreateParameter(new string[] + { + $"{ServerPermissions.ViewCategories.GetNumberString()}:1", + $"{ServerPermissions.ViewChannels.GetNumberString()}:1", + }) + ); +} + +if (!Tables.Categories.TryRead(Categories.ID, out _, Categories.ID.CreateParameter(0))) +{ + Tables.Categories.Insert( + Categories.ID.CreateParameter(0), + Categories.Name.CreateParameter("server"), + Categories.Description.CreateParameter( + "The default category for the server. Everybody will see this category."), + Categories.RoleOverides.CreateParameter(new long[1] { 0 }) + ); +} + + + +var builder = WebApplication.CreateBuilder( args ); + +// Add services to the container. + +builder.Services.AddControllers(); +builder.Services.AddProblemDetails(); + +builder.Services.AddApiVersioning( + options => + { + // reporting api versions will return the headers + // "api-supported-versions" and "api-deprecated-versions" + options.ReportApiVersions = true; + /* Sunset example + options.Policies.Sunset( 1 ) + //.Effective( DateTimeOffset.Now.Subtract( new TimeSpan(9999999) ) ) + .Effective( DateTimeOffset.Now.AddDays( 2 ) ) + .Link( "policy.html" ) + .Title( "Versioning Policy" ) + .Type( "text/html" ); + */ + } ) + .AddMvc() + .AddApiExplorer( + options => + { + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + options.GroupNameFormat = "'v'VVV"; + + // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // can also be used to control the format of the API version in route templates + options.SubstituteApiVersionInUrl = true; + } ); +builder.Services.Configure(options => +{ + options.InvalidModelStateResponseFactory = actionContext => + { + string content; + using (StreamReader reader = new(actionContext.HttpContext.Request.Body)) + { + content = reader.ReadToEndAsync().Result; + } + + if (string.IsNullOrEmpty(content)) return new ObjectResult(new HTTPResponse() + { + error = ErrorCode.MissingPostData, + error_message = "The post information was missing" + }) + { + StatusCode = 403 + }; + return new ObjectResult(new HTTPResponse() + { + error = ErrorCode.InvalidPostData, + error_message = "The provided post information was invalid" + }) + { + StatusCode = 403 + }; + }; +}); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddTransient, ConfigureSwaggerOptions>(); +builder.Services.AddSwaggerGen( + options => + { + // add a custom operation filter which sets default values + options.OperationFilter(); + + var fileName = typeof(Program).Assembly.GetName().Name + ".xml"; + var filePath = Path.Combine(AppContext.BaseDirectory, fileName); + + // integrate xml comments + options.IncludeXmlComments(filePath); + } ); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. + +app.UseSwagger(); +app.UseSwaggerUI( + options => + { + IReadOnlyList descriptions = app.DescribeApiVersions(); + + // build a swagger endpoint for each discovered API version + foreach ( var description in descriptions ) + { + string url = $"/swagger/{description.GroupName}/swagger.json"; + string name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + options.InjectStylesheet("/www/css/SwaggerDark.css"); + options.DocumentTitle = "Luski Server API"; + } ); +app.MapGet("/www/css/SwaggerDark.css", async (CancellationToken t) => +{ + var css = await File.ReadAllBytesAsync("www/css/SwaggerDark.css", t); + return Results.File(css, "text/css"); +}).ExcludeFromDescription(); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); +app.Run(); \ No newline at end of file diff --git a/LuskiServer/Properties/launchSettings.json b/LuskiServer/Properties/launchSettings.json new file mode 100644 index 0000000..3a1eef0 --- /dev/null +++ b/LuskiServer/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:63790", + "sslPort": 44369 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5288;http://10.100.0.153:5287", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7173;http://localhost:5287", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/LuskiServer/SwaggerDefaultValues.cs b/LuskiServer/SwaggerDefaultValues.cs new file mode 100644 index 0000000..09c8dbb --- /dev/null +++ b/LuskiServer/SwaggerDefaultValues.cs @@ -0,0 +1,65 @@ +namespace LuskiServer; + +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text.Json; + +/// +/// Represents the OpenAPI/Swashbuckle operation filter used to document information provided, but not used. +/// +/// This is only required due to bugs in the . +/// Once they are fixed and published, this class can be removed. +public class SwaggerDefaultValues : IOperationFilter +{ + /// + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + ApiDescription apiDescription = context.ApiDescription; + + operation.Deprecated |= apiDescription.IsDeprecated(); + + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 + foreach (ApiResponseType responseType in context.ApiDescription.SupportedResponseTypes) + { + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 + string responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); + OpenApiResponse response = operation.Responses[responseKey]; + + foreach (string contentType in response.Content.Keys) + { + if (!responseType.ApiResponseFormats.Any(x => x.MediaType == contentType)) + { + response.Content.Remove(contentType); + } + } + } + + if (operation.Parameters == null) + { + return; + } + + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 + foreach (OpenApiParameter parameter in operation.Parameters) + { + ApiParameterDescription description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + parameter.Description ??= description.ModelMetadata?.Description; + + if (parameter.Schema.Default == null && + description.DefaultValue != null && + description.DefaultValue is not DBNull && + description.ModelMetadata is ModelMetadata modelMetadata ) + { + // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 + string json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); + parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); + } + + parameter.Required |= description.IsRequired; + } + } +} \ No newline at end of file diff --git a/LuskiServer/appsettings.Development.json b/LuskiServer/appsettings.Development.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/LuskiServer/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/LuskiServer/appsettings.json b/LuskiServer/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/LuskiServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/LuskiServer/www/css/SwaggerDark.css b/LuskiServer/www/css/SwaggerDark.css new file mode 100644 index 0000000..e32c281 --- /dev/null +++ b/LuskiServer/www/css/SwaggerDark.css @@ -0,0 +1,829 @@ +a { color: #8c8cfa; } + +::-webkit-scrollbar-track-piece { background-color: rgba(255, 255, 255, .2) !important; } + +::-webkit-scrollbar-track { background-color: rgba(255, 255, 255, .3) !important; } + +::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .5) !important; } + +embed[type="application/pdf"] { filter: invert(90%); } + +html { + background: #1f1f1f !important; + box-sizing: border-box; + filter: contrast(100%) brightness(100%) saturate(100%); + overflow-y: scroll; +} + +body { + background: #1f1f1f; + background-color: #1f1f1f; + background-image: none !important; +} + +button, input, select, textarea { + background-color: #1f1f1f; + color: #bfbfbf; +} + +font, html { color: #bfbfbf; } + +.swagger-ui, .swagger-ui section h3 { color: #b5bac9; } + +.swagger-ui a { background-color: transparent; } + +.swagger-ui mark { + background-color: #664b00; + color: #bfbfbf; +} + +.swagger-ui legend { color: inherit; } + +.swagger-ui .debug * { outline: #e6da99 solid 1px; } + +.swagger-ui .debug-white * { outline: #fff solid 1px; } + +.swagger-ui .debug-black * { outline: #bfbfbf solid 1px; } + +.swagger-ui .debug-grid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0; } + +.swagger-ui .debug-grid-16 { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0; } + +.swagger-ui .debug-grid-8-solid { background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21; } + +.swagger-ui .debug-grid-16-solid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21; } + +.swagger-ui .b--black { border-color: #000; } + +.swagger-ui .b--near-black { border-color: #121212; } + +.swagger-ui .b--dark-gray { border-color: #333; } + +.swagger-ui .b--mid-gray { border-color: #545454; } + +.swagger-ui .b--gray { border-color: #787878; } + +.swagger-ui .b--silver { border-color: #999; } + +.swagger-ui .b--light-silver { border-color: #6e6e6e; } + +.swagger-ui .b--moon-gray { border-color: #4d4d4d; } + +.swagger-ui .b--light-gray { border-color: #2b2b2b; } + +.swagger-ui .b--near-white { border-color: #242424; } + +.swagger-ui .b--white { border-color: #1c1c21; } + +.swagger-ui .b--white-90 { border-color: rgba(28, 28, 33, .9); } + +.swagger-ui .b--white-80 { border-color: rgba(28, 28, 33, .8); } + +.swagger-ui .b--white-70 { border-color: rgba(28, 28, 33, .7); } + +.swagger-ui .b--white-60 { border-color: rgba(28, 28, 33, .6); } + +.swagger-ui .b--white-50 { border-color: rgba(28, 28, 33, .5); } + +.swagger-ui .b--white-40 { border-color: rgba(28, 28, 33, .4); } + +.swagger-ui .b--white-30 { border-color: rgba(28, 28, 33, .3); } + +.swagger-ui .b--white-20 { border-color: rgba(28, 28, 33, .2); } + +.swagger-ui .b--white-10 { border-color: rgba(28, 28, 33, .1); } + +.swagger-ui .b--white-05 { border-color: rgba(28, 28, 33, .05); } + +.swagger-ui .b--white-025 { border-color: rgba(28, 28, 33, .024); } + +.swagger-ui .b--white-0125 { border-color: rgba(28, 28, 33, .01); } + +.swagger-ui .b--black-90 { border-color: rgba(0, 0, 0, .9); } + +.swagger-ui .b--black-80 { border-color: rgba(0, 0, 0, .8); } + +.swagger-ui .b--black-70 { border-color: rgba(0, 0, 0, .7); } + +.swagger-ui .b--black-60 { border-color: rgba(0, 0, 0, .6); } + +.swagger-ui .b--black-50 { border-color: rgba(0, 0, 0, .5); } + +.swagger-ui .b--black-40 { border-color: rgba(0, 0, 0, .4); } + +.swagger-ui .b--black-30 { border-color: rgba(0, 0, 0, .3); } + +.swagger-ui .b--black-20 { border-color: rgba(0, 0, 0, .2); } + +.swagger-ui .b--black-10 { border-color: rgba(0, 0, 0, .1); } + +.swagger-ui .b--black-05 { border-color: rgba(0, 0, 0, .05); } + +.swagger-ui .b--black-025 { border-color: rgba(0, 0, 0, .024); } + +.swagger-ui .b--black-0125 { border-color: rgba(0, 0, 0, .01); } + +.swagger-ui .b--dark-red { border-color: #bc2f36; } + +.swagger-ui .b--red { border-color: #c83932; } + +.swagger-ui .b--light-red { border-color: #ab3c2b; } + +.swagger-ui .b--orange { border-color: #cc6e33; } + +.swagger-ui .b--purple { border-color: #5e2ca5; } + +.swagger-ui .b--light-purple { border-color: #672caf; } + +.swagger-ui .b--dark-pink { border-color: #ab2b81; } + +.swagger-ui .b--hot-pink { border-color: #c03086; } + +.swagger-ui .b--pink { border-color: #8f2464; } + +.swagger-ui .b--light-pink { border-color: #721d4d; } + +.swagger-ui .b--dark-green { border-color: #1c6e50; } + +.swagger-ui .b--green { border-color: #279b70; } + +.swagger-ui .b--light-green { border-color: #228762; } + +.swagger-ui .b--navy { border-color: #0d1d35; } + +.swagger-ui .b--dark-blue { border-color: #20497e; } + +.swagger-ui .b--blue { border-color: #4380d0; } + +.swagger-ui .b--light-blue { border-color: #20517e; } + +.swagger-ui .b--lightest-blue { border-color: #143a52; } + +.swagger-ui .b--washed-blue { border-color: #0c312d; } + +.swagger-ui .b--washed-green { border-color: #0f3d2c; } + +.swagger-ui .b--washed-red { border-color: #411010; } + +.swagger-ui .b--transparent { border-color: transparent; } + +.swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow { border-color: #664b00; } + +.swagger-ui .shadow-1 { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + +.swagger-ui .shadow-2 { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + +.swagger-ui .shadow-3 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + +.swagger-ui .shadow-4 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + +.swagger-ui .shadow-5 { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } + +@media screen and (min-width: 30em) { + .swagger-ui .shadow-1-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-ns { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } +} + +@media screen and (max-width: 60em) and (min-width: 30em) { + .swagger-ui .shadow-1-m { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-m { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-m { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } +} + +@media screen and (min-width: 60em) { + .swagger-ui .shadow-1-l { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; } + + .swagger-ui .shadow-2-l { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; } + + .swagger-ui .shadow-3-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; } + + .swagger-ui .shadow-4-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; } + + .swagger-ui .shadow-5-l { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; } +} + +.swagger-ui .black-05 { color: rgba(191, 191, 191, .05); } + +.swagger-ui .bg-black-05 { background-color: rgba(0, 0, 0, .05); } + +.swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover { color: rgba(191, 191, 191, .9); } + +.swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover { color: rgba(191, 191, 191, .8); } + +.swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover { color: rgba(191, 191, 191, .7); } + +.swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover { color: rgba(191, 191, 191, .6); } + +.swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover { color: rgba(191, 191, 191, .5); } + +.swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover { color: rgba(191, 191, 191, .4); } + +.swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover { color: rgba(191, 191, 191, .3); } + +.swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover { color: rgba(191, 191, 191, .2); } + +.swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover { color: rgba(191, 191, 191, .1); } + +.swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 { color: rgba(255, 255, 255, .9); } + +.swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 { color: rgba(255, 255, 255, .8); } + +.swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 { color: rgba(255, 255, 255, .7); } + +.swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 { color: rgba(255, 255, 255, .6); } + +.swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 { color: rgba(255, 255, 255, .5); } + +.swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 { color: rgba(255, 255, 255, .4); } + +.swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 { color: rgba(255, 255, 255, .3); } + +.swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 { color: rgba(255, 255, 255, .2); } + +.swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 { color: rgba(255, 255, 255, .1); } + +.swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray { color: #ccc; } + +.swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray { color: #ededed; } + +.swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white { color: #f5f5f5; } + +.swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover { color: #e6999d; } + +.swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red { color: #e69d99; } + +.swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red { color: #e6a399; } + +.swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange { color: #e6b699; } + +.swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover { color: #e6d099; } + +.swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow { color: #e6da99; } + +.swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow { color: #ede6b6; } + +.swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple { color: #b99ae4; } + +.swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple { color: #bb99e6; } + +.swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover { color: #e699cc; } + +.swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink { color: #e699c7; } + +.swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink { color: #edb6d5; } + +.swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover { color: #99e6c9; } + +.swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green { color: #a1e8ce; } + +.swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy { color: #99b8e6; } + +.swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover { color: #99bae6; } + +.swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue { color: #a9cbea; } + +.swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue { color: #d6e9f5; } + +.swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue { color: #f7fdfc; } + +.swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green { color: #ebfaf4; } + +.swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow { color: #fbf9ef; } + +.swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red { color: #f9e7e7; } + +.swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover { color: inherit; } + +.swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover { background-color: rgba(0, 0, 0, .9); } + +.swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover { background-color: rgba(0, 0, 0, .8); } + +.swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover { background-color: rgba(0, 0, 0, .7); } + +.swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover { background-color: rgba(0, 0, 0, .6); } + +.swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover { background-color: rgba(0, 0, 0, .5); } + +.swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover { background-color: rgba(0, 0, 0, .4); } + +.swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover { background-color: rgba(0, 0, 0, .3); } + +.swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover { background-color: rgba(0, 0, 0, .2); } + +.swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover { background-color: rgba(28, 28, 33, .9); } + +.swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover { background-color: rgba(28, 28, 33, .8); } + +.swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover { background-color: rgba(28, 28, 33, .7); } + +.swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover { background-color: rgba(28, 28, 33, .6); } + +.swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover { background-color: rgba(28, 28, 33, .5); } + +.swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover { background-color: rgba(28, 28, 33, .4); } + +.swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover { background-color: rgba(28, 28, 33, .3); } + +.swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover { background-color: rgba(28, 28, 33, .2); } + +.swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover { background-color: #000; } + +.swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover { background-color: #121212; } + +.swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover { background-color: #333; } + +.swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover { background-color: #545454; } + +.swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover { background-color: #787878; } + +.swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover { background-color: #999; } + +.swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover { background-color: #1c1c21; } + +.swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover { background-color: transparent; } + +.swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover { background-color: #bc2f36; } + +.swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover { background-color: #c83932; } + +.swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover { background-color: #ab3c2b; } + +.swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover { background-color: #cc6e33; } + +.swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover { background-color: #664b00; } + +.swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover { background-color: #5e2ca5; } + +.swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover { background-color: #672caf; } + +.swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover { background-color: #ab2b81; } + +.swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover { background-color: #c03086; } + +.swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover { background-color: #8f2464; } + +.swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover { background-color: #721d4d; } + +.swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover { background-color: #1c6e50; } + +.swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover { background-color: #279b70; } + +.swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover { background-color: #228762; } + +.swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover { background-color: #0d1d35; } + +.swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover { background-color: #20497e; } + +.swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover { background-color: #4380d0; } + +.swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover { background-color: #20517e; } + +.swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover { background-color: #143a52; } + +.swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover { background-color: #0c312d; } + +.swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover { background-color: #0f3d2c; } + +.swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover { background-color: #411010; } + +.swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover { background-color: inherit; } + +.swagger-ui .shadow-hover { transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s; } + +.swagger-ui .shadow-hover::after { + border-radius: inherit; + box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px; + content: ""; + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s; + width: 100%; + z-index: -1; +} + +.swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover { transition: background-color .15s ease-in-out 0s; } + +.swagger-ui .nested-links a { + color: #99bae6; + transition: color .15s ease-in 0s; +} + +.swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover { + color: #a9cbea; + transition: color .15s ease-in 0s; +} + +.swagger-ui .opblock-tag { + border-bottom: 1px solid rgba(58, 64, 80, .3); + color: #b5bac9; + transition: all .2s ease 0s; +} + +.swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg { transition: all .4s ease 0s; } + +.swagger-ui .opblock { + border: 1px solid #000; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, .19) 0 0 3px; + margin: 0 0 15px; +} + +.swagger-ui .opblock .tab-header .tab-item.active h4 span::after { background: gray; } + +.swagger-ui .opblock.is-open .opblock-summary { border-bottom: 1px solid #000; } + +.swagger-ui .opblock .opblock-section-header { + background: rgba(28, 28, 33, .8); + box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; +} + +.swagger-ui .opblock .opblock-section-header > label > span { padding: 0 10px 0 0; } + +.swagger-ui .opblock .opblock-summary-method { + background: #000; + color: #fff; + text-shadow: rgba(0, 0, 0, .1) 0 1px 0; +} + +.swagger-ui .opblock.opblock-post { + background: rgba(72, 203, 144, .1); + border-color: #48cb90; +} + +.swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after { background: #48cb90; } + +.swagger-ui .opblock.opblock-post .opblock-summary { border-color: #48cb90; } + +.swagger-ui .opblock.opblock-put { + background: rgba(213, 157, 88, .1); + border-color: #d59d58; +} + +.swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after { background: #d59d58; } + +.swagger-ui .opblock.opblock-put .opblock-summary { border-color: #d59d58; } + +.swagger-ui .opblock.opblock-delete { + background: rgba(200, 50, 50, .1); + border-color: #c83232; +} + +.swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after { background: #c83232; } + +.swagger-ui .opblock.opblock-delete .opblock-summary { border-color: #c83232; } + +.swagger-ui .opblock.opblock-get { + background: rgba(42, 105, 167, .1); + border-color: #2a69a7; +} + +.swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after { background: #2a69a7; } + +.swagger-ui .opblock.opblock-get .opblock-summary { border-color: #2a69a7; } + +.swagger-ui .opblock.opblock-patch { + background: rgba(92, 214, 188, .1); + border-color: #5cd6bc; +} + +.swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after { background: #5cd6bc; } + +.swagger-ui .opblock.opblock-patch .opblock-summary { border-color: #5cd6bc; } + +.swagger-ui .opblock.opblock-head { + background: rgba(140, 63, 207, .1); + border-color: #8c3fcf; +} + +.swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after { background: #8c3fcf; } + +.swagger-ui .opblock.opblock-head .opblock-summary { border-color: #8c3fcf; } + +.swagger-ui .opblock.opblock-options { + background: rgba(36, 89, 143, .1); + border-color: #24598f; +} + +.swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after { background: #24598f; } + +.swagger-ui .opblock.opblock-options .opblock-summary { border-color: #24598f; } + +.swagger-ui .opblock.opblock-deprecated { + background: rgba(46, 46, 46, .1); + border-color: #2e2e2e; + opacity: .6; +} + +.swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after { background: #2e2e2e; } + +.swagger-ui .opblock.opblock-deprecated .opblock-summary { border-color: #2e2e2e; } + +.swagger-ui .filter .operation-filter-input { border: 2px solid #2b3446; } + +.swagger-ui .tab li:first-of-type::after { background: rgba(0, 0, 0, .2); } + +.swagger-ui .download-contents { + background: #7c8192; + color: #fff; +} + +.swagger-ui .scheme-container { + background: #1c1c21; + box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0; +} + +.swagger-ui .loading-container .loading::before { + animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity; + border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1); +} + +.swagger-ui .response-control-media-type--accept-controller select { border-color: #196619; } + +.swagger-ui .response-control-media-type__accept-message { color: #99e699; } + +.swagger-ui .version-pragma__message code { background-color: #3b3b3b; } + +.swagger-ui .btn { + background: 0 0; + border: 2px solid gray; + box-shadow: rgba(0, 0, 0, .1) 0 1px 2px; + color: #b5bac9; +} + +.swagger-ui .btn:hover { box-shadow: rgba(0, 0, 0, .3) 0 0 5px; } + +.swagger-ui .btn.authorize, .swagger-ui .btn.cancel { + background-color: transparent; + border-color: #a72a2a; + color: #e69999; +} + +.swagger-ui .btn.authorize { + border-color: #48cb90; + color: #9ce3c3; +} + +.swagger-ui .btn.authorize svg { fill: #9ce3c3; } + +.swagger-ui .btn.execute { + background-color: #5892d5; + border-color: #5892d5; + color: #fff; +} + +.swagger-ui .copy-to-clipboard { background: #7c8192; } + +.swagger-ui .copy-to-clipboard button { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat; } + +.swagger-ui select { + background: url("data:image/svg+xml;charset=utf-8,") right 10px center/20px no-repeat #212121; + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21; + border: 2px solid #41444e; +} + +.swagger-ui select[multiple] { background: #212121; } + +.swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid { + background: #390e0e; + border-color: #c83232; +} + +.swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea { + background: #1c1c21; + border: 1px solid #404040; +} + +.swagger-ui textarea { + background: rgba(28, 28, 33, .8); + color: #b5bac9; +} + +.swagger-ui input[disabled], .swagger-ui select[disabled] { + background-color: #1f1f1f; + color: #bfbfbf; +} + +.swagger-ui textarea[disabled] { + background-color: #41444e; + color: #fff; +} + +.swagger-ui select[disabled] { border-color: #878787; } + +.swagger-ui textarea:focus { border: 2px solid #2a69a7; } + +.swagger-ui .checkbox input[type=checkbox] + label > .item { + background: #303030; + box-shadow: #303030 0 0 0 2px; +} + +.swagger-ui .checkbox input[type=checkbox]:checked + label > .item { background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030; } + +.swagger-ui .dialog-ux .backdrop-ux { background: rgba(0, 0, 0, .8); } + +.swagger-ui .dialog-ux .modal-ux { + background: #1c1c21; + border: 1px solid #2e2e2e; + box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0; +} + +.swagger-ui .dialog-ux .modal-ux-header .close-modal { background: 0 0; } + +.swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td { color: #bfbfbf !important; } + +.swagger-ui .model-toggle::after { background: url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat; } + +.swagger-ui .model-hint { + background: rgba(0, 0, 0, .7); + color: #ebebeb; +} + +.swagger-ui section.models { border: 1px solid rgba(58, 64, 80, .3); } + +.swagger-ui section.models.is-open h4 { border-bottom: 1px solid rgba(58, 64, 80, .3); } + +.swagger-ui section.models .model-container { background: rgba(0, 0, 0, .05); } + +.swagger-ui section.models .model-container:hover { background: rgba(0, 0, 0, .07); } + +.swagger-ui .model-box { background: rgba(0, 0, 0, .1); } + +.swagger-ui .prop-type { color: #aaaad4; } + +.swagger-ui table thead tr td, .swagger-ui table thead tr th { + border-bottom: 1px solid rgba(58, 64, 80, .2); + color: #b5bac9; +} + +.swagger-ui .parameter__name.required::after { color: rgba(230, 153, 153, .6); } + +.swagger-ui .topbar .download-url-wrapper .select-label { color: #f0f0f0; } + +.swagger-ui .topbar .download-url-wrapper .download-url-button { + background: #63a040; + color: #fff; +} + +.swagger-ui .info .title small { background: #7c8492; } + +.swagger-ui .info .title small.version-stamp { background-color: #7a9b27; } + +.swagger-ui .auth-container .errors { + background-color: #350d0d; + color: #b5bac9; +} + +.swagger-ui .errors-wrapper { + background: rgba(200, 50, 50, .1); + border: 2px solid #c83232; +} + +.swagger-ui .markdown code, .swagger-ui .renderedmarkdown code { + background: rgba(0, 0, 0, .05); + color: #c299e6; +} + +.swagger-ui .model-toggle:after { background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat; } + +.swagger-ui .expand-operation svg, .swagger-ui section.models h4 svg { fill: #fff; } + +::-webkit-scrollbar-track { background-color: #646464 !important; } + +::-webkit-scrollbar-thumb { + background-color: #242424 !important; + border: 2px solid #3e4346 !important; +} + +::-webkit-scrollbar-button:vertical:start:decrement { + background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:vertical:end:increment { + background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:horizontal:end:increment { + background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:horizontal:start:decrement { + background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece { background-color: #3e4346 !important; } + +.swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension { color: #bfbfbf; } + +.swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white { color: #fff; } + +.swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) { background-color: rgba(0, 0, 0, .1); } + +.swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) { background-color: rgba(28, 28, 33, .1); } + +.swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) { background-color: #6e6e6e; } + +.swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) { background-color: #4d4d4d; } + +.swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) { background-color: #2b2b2b; } + +.swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) { background-color: #242424; } + +.swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover { background: rgba(0, 0, 0, .02); } + +.swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td { color: #b5bac9; } + +.swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star { color: #e69999; } + +.swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl { + background: #41444e; + border-radius: 4px; + color: #fff; +} + +.swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg { fill: #bfbfbf; } + +.swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header { border-bottom: 1px solid #2e2e2e; } + +.swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] { border: 2px solid #63a040; } + +.swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a { color: #99bde6; } + +/* Dark Scrollbar */ +::-webkit-scrollbar { + width: 14px; + height: 14px; +} + +::-webkit-scrollbar-button { + background-color: #3e4346 !important; +} + +::-webkit-scrollbar-track { + background-color: #646464 !important; +} + +::-webkit-scrollbar-track-piece { + background-color: #3e4346 !important; +} + +::-webkit-scrollbar-thumb { + height: 50px; + background-color: #242424 !important; + border: 2px solid #3e4346 !important; +} + +::-webkit-scrollbar-corner {} + +::-webkit-resizer {} + +::-webkit-scrollbar-button:vertical:start:decrement { + background: + linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), + linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:vertical:end:increment { + background: + linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:horizontal:end:increment { + background: + linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; +} + +::-webkit-scrollbar-button:horizontal:start:decrement { + background: + linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%), + linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%); + background-color: #b6b6b6; +}