From 77c6ee00eaf54c1646ff90e85dcb0121a7047d44 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 18:39:48 -0400 Subject: [PATCH 1/9] JWT symmetric keys Updated config file to store neccessary information --- appsettings.Development.json | 6 ++++++ appsettings.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/appsettings.Development.json b/appsettings.Development.json index cd727fb..c8ed0de 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -16,6 +16,12 @@ "PrivateKeyPath": "", "PublicKeyPath": "" }, + "JWT": { + "Issuer": "", + "Audience": "", + "Secret": "", + "Subject": "" + }, "ConnectionStrings": { "DefaultConnection": "Server=;Database=;Uid=;Pwd=;" }, diff --git a/appsettings.json b/appsettings.json index 68eb482..71f2123 100644 --- a/appsettings.json +++ b/appsettings.json @@ -16,6 +16,12 @@ "PrivateKeyPath": "", "PublicKeyPath": "" }, + "JWT": { + "Issuer": "", + "Audience": "", + "Secret": "", + "Subject": "" + }, "ConnectionStrings": { "DefaultConnection": "Server=;Database=;Uid=;Pwd=;" }, -- 2.47.3 From 00ef8d024298324cc277a43b4fab04719b0e5c84 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 18:56:56 -0400 Subject: [PATCH 2/9] Added JWT bearer authentication --- Startup.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Startup.cs b/Startup.cs index 6dea1e4..af349a3 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,5 +1,7 @@ using System; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -72,6 +74,20 @@ namespace Icarus var connString = Configuration.GetConnectionString("DefaultConnection"); + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => + { + options.RequireHttpsMetadata = false; + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuer = true, + ValidateAudience = true, + ValidAudience = Configuration["JWT:Audience"], + ValidIssuer = Configuration["JWT:Issuer"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"])) + }; + }); + services.AddDbContext(options => options.UseMySQL(connString)); services.AddDbContext(options => options.UseMySQL(connString)); services.AddDbContext(options => options.UseMySQL(connString)); -- 2.47.3 From d5944c470b9272c6b059b6ea9cf8dac174810dbf Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 20:34:33 -0400 Subject: [PATCH 3/9] Symmetric keys with jwt has been implemented --- Controllers/Managers/TokenManager.cs | 55 +++++++++++++++++++++++--- Controllers/v1/LoginController.cs | 49 ++++++++++++++--------- Controllers/v1/SongStreamController.cs | 3 ++ Startup.cs | 2 +- 4 files changed, 83 insertions(+), 26 deletions(-) diff --git a/Controllers/Managers/TokenManager.cs b/Controllers/Managers/TokenManager.cs index c00452f..08c0a15 100644 --- a/Controllers/Managers/TokenManager.cs +++ b/Controllers/Managers/TokenManager.cs @@ -4,10 +4,14 @@ using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; using JWT; using JWT.Serializers; using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; @@ -113,6 +117,40 @@ namespace Icarus.Controllers.Managers }; } + public LoginResult LoginSymmetric(User user) + { + var tokenResult = new TokenTierOne(); + tokenResult.TokenType = "Jwt"; + + var payload = Payload(); + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JWT:Secret"])); + var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var token = new JwtSecurityToken( + _config["JWT:Issuer"], + _config["JWT:Audience"], + payload, + expires: DateTime.UtcNow.AddMinutes(30), + signingCredentials: signIn); + + tokenResult.AccessToken = new JwtSecurityTokenHandler().WriteToken(token); + + var expClaim = payload.FirstOrDefault(cl => + { + return cl.Type.Equals("exp"); + }); + + var expiredDate = DateTime.Parse(expClaim.Value); + var exp = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds); + tokenResult.Expiration = Convert.ToInt32(exp); + + return new LoginResult + { + UserID = user.UserID, Username = user.Username, Token = tokenResult.AccessToken, + TokenType = tokenResult.TokenType, Expiration = tokenResult.Expiration, + Message = "Successfully retrieved token" + }; + } + public bool IsTokenValid(string scope, string accessToken) { var result = false; @@ -201,23 +239,28 @@ namespace Icarus.Controllers.Managers private List Payload() { - const int expLimit = 24; + var expLimit = 30; var currentDate = DateTime.Now; - var expiredDate = currentDate.AddHours(expLimit); + var expiredDate = currentDate.AddMinutes(expLimit); var issued = Math.Floor((currentDate - DateTime.UnixEpoch).TotalSeconds); var expires = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds); var issuer = "https://soaricarus.auth0.com"; issuer = "http://localhost:5002"; var audience = "https://icarus/api"; audience = "http://localhost:5002"; + var subject = _config["JWT:Subject"]; var claim = new List() { new System.Security.Claims.Claim("scope", AllScopes(), "string"), - new System.Security.Claims.Claim("exp", $"{expires}", "integer"), - new System.Security.Claims.Claim("aud", $"{audience}", "string"), - new System.Security.Claims.Claim("iss", $"{issuer}", "string"), - new System.Security.Claims.Claim("iat", $"{issued}", "integer") + // new System.Security.Claims.Claim("exp", $"{expires}", "integer"), + new System.Security.Claims.Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString()), + // new System.Security.Claims.Claim("aud", $"{audience}", "string"), + new System.Security.Claims.Claim(JwtRegisteredClaimNames.Aud, audience), + // new System.Security.Claims.Claim("iss", $"{issuer}", "string"), + new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iss, issuer), + new Claim(JwtRegisteredClaimNames.Sub, subject), + new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iat, currentDate.ToString()) }; return claim; diff --git a/Controllers/v1/LoginController.cs b/Controllers/v1/LoginController.cs index 78ee567..881e3d9 100644 --- a/Controllers/v1/LoginController.cs +++ b/Controllers/v1/LoginController.cs @@ -55,34 +55,45 @@ namespace Icarus.Controllers.V1 Username = user.Username }; - if (context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username)) != null) + try { - user = context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username)); - - var validatePass = new PasswordEncryption(); - var validated = validatePass.VerifyPassword(user, password); - if (!validated) + if (context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username)) != null) { - loginRes.Message = message; - _logger.LogInformation(message); + user = context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username)); + + var validatePass = new PasswordEncryption(); + var validated = validatePass.VerifyPassword(user, password); + if (!validated) + { + loginRes.Message = message; + _logger.LogInformation(message); + + return Ok(loginRes); + } + + _logger.LogInformation("Successfully validated user credentials"); + + TokenManager tk = new TokenManager(_config); + + // loginRes = tk.LogIn(user); + loginRes = tk.LoginSymmetric(user); return Ok(loginRes); } + else + { + loginRes.Message = message; - _logger.LogInformation("Successfully validated user credentials"); - - TokenManager tk = new TokenManager(_config); - - loginRes = tk.LogIn(user); - - return Ok(loginRes); + return NotFound(loginRes); + } } - else + catch (Exception ex) { - loginRes.Message = message; - - return NotFound(loginRes); + _logger.LogError("An error occurred: {0}", ex.Message); + _logger.LogError("Inner Exception: {0}", ex.InnerException.Message); } + + return NotFound(loginRes); } #endregion } diff --git a/Controllers/v1/SongStreamController.cs b/Controllers/v1/SongStreamController.cs index b273a08..4479800 100644 --- a/Controllers/v1/SongStreamController.cs +++ b/Controllers/v1/SongStreamController.cs @@ -20,6 +20,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/song/stream")] [ApiController] + [Authorize] public class SongStreamController : BaseController { #region Fields @@ -46,10 +47,12 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public async Task Get(int id) { + /** if (!IsTokenValid("stream:songs")) { return StatusCode(401, "Not allowed"); } + */ var context = new SongContext(_config.GetConnectionString("DefaultConnection")); diff --git a/Startup.cs b/Startup.cs index af349a3..7462694 100644 --- a/Startup.cs +++ b/Startup.cs @@ -95,7 +95,7 @@ namespace Icarus services.AddDbContext(options => options.UseMySQL(connString)); services.AddDbContext(options => options.UseMySQL(connString)); - services.AddAsymmetricAuthentication(Configuration); + // services.AddAsymmetricAuthentication(Configuration); /** services.AddTransient(au => new AuthenticationService(new UserService(Configuration), new TokenService(Configuration), Configuration)); -- 2.47.3 From 05b5de0939b6f06a68843f5bf1d1ceaafbcff7dc Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 20:45:17 -0400 Subject: [PATCH 4/9] Cleanup --- Controllers/Managers/TokenManager.cs | 3 -- Controllers/v1/AlbumController.cs | 11 +----- Controllers/v1/ArtistController.cs | 6 +-- Controllers/v1/CoverArtController.cs | 11 +----- Controllers/v1/GenreController.cs | 11 +----- Controllers/v1/LoginController.cs | 4 -- .../v1/SongCompressedDataControllers.cs | 6 +-- Controllers/v1/SongController.cs | 16 +------- Controllers/v1/SongDataController.cs | 21 +--------- Controllers/v1/SongStreamController.cs | 7 ---- Startup.cs | 39 ------------------- 11 files changed, 7 insertions(+), 128 deletions(-) diff --git a/Controllers/Managers/TokenManager.cs b/Controllers/Managers/TokenManager.cs index 08c0a15..4f819de 100644 --- a/Controllers/Managers/TokenManager.cs +++ b/Controllers/Managers/TokenManager.cs @@ -253,11 +253,8 @@ namespace Icarus.Controllers.Managers var claim = new List() { new System.Security.Claims.Claim("scope", AllScopes(), "string"), - // new System.Security.Claims.Claim("exp", $"{expires}", "integer"), new System.Security.Claims.Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString()), - // new System.Security.Claims.Claim("aud", $"{audience}", "string"), new System.Security.Claims.Claim(JwtRegisteredClaimNames.Aud, audience), - // new System.Security.Claims.Claim("iss", $"{issuer}", "string"), new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iss, issuer), new Claim(JwtRegisteredClaimNames.Sub, subject), new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iat, currentDate.ToString()) diff --git a/Controllers/v1/AlbumController.cs b/Controllers/v1/AlbumController.cs index 903494a..8a3a259 100644 --- a/Controllers/v1/AlbumController.cs +++ b/Controllers/v1/AlbumController.cs @@ -14,6 +14,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/album")] [ApiController] + [Authorize] public class AlbumController : BaseController { #region Fields @@ -40,11 +41,6 @@ namespace Icarus.Controllers.V1 [HttpGet] public IActionResult Get() { - if (!IsTokenValid("read:albums")) - { - return StatusCode(401, "Not allowed"); - } - List albums = new List(); var albumContext = new AlbumContext(_connectionString); @@ -60,11 +56,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public IActionResult Get(int id) { - if (!IsTokenValid("read:albums")) - { - return StatusCode(401, "Not allowed"); - } - Album album = new Album { AlbumID = id diff --git a/Controllers/v1/ArtistController.cs b/Controllers/v1/ArtistController.cs index 51e21b2..a27f48a 100644 --- a/Controllers/v1/ArtistController.cs +++ b/Controllers/v1/ArtistController.cs @@ -13,6 +13,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/artist")] [ApiController] + [Authorize] public class ArtistController : BaseController { #region Fields @@ -39,11 +40,6 @@ namespace Icarus.Controllers.V1 [HttpGet] public IActionResult Get() { - if (!IsTokenValid("read:artists")) - { - return StatusCode(401, "Not allowed"); - } - var artistContext = new ArtistContext(_connectionString); var artists = artistContext.Artists.ToList(); diff --git a/Controllers/v1/CoverArtController.cs b/Controllers/v1/CoverArtController.cs index 482b6a4..2dd422a 100644 --- a/Controllers/v1/CoverArtController.cs +++ b/Controllers/v1/CoverArtController.cs @@ -15,6 +15,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/coverart")] [ApiController] + [Authorize] public class CoverArtController : BaseController { #region Fields @@ -36,11 +37,6 @@ namespace Icarus.Controllers.V1 #region HTTP Routes public IActionResult Get() { - if (!IsTokenValid("read:songs")) - { - return StatusCode(401, "Not allowed"); - } - var coverArtContext = new CoverArtContext(_connectionString); var coverArtRecords = coverArtContext.CoverArtImages.ToList(); @@ -60,11 +56,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public async Task Get(int id) { - if (!IsTokenValid("download:cover_art")) - { - return StatusCode(401, "Not allowed"); - } - var coverArt = new CoverArt { CoverArtID = id }; var coverArtContext = new CoverArtContext(_connectionString); diff --git a/Controllers/v1/GenreController.cs b/Controllers/v1/GenreController.cs index 8e29502..ae553e6 100644 --- a/Controllers/v1/GenreController.cs +++ b/Controllers/v1/GenreController.cs @@ -14,6 +14,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/genre")] [ApiController] + [Authorize] public class GenreController : BaseController { #region Fields @@ -40,11 +41,6 @@ namespace Icarus.Controllers.V1 [HttpGet] public IActionResult Get() { - if (!IsTokenValid("read:genre")) - { - return StatusCode(401, "Not allowed"); - } - var genres = new List(); var genreStore = new GenreContext(_connectionString); @@ -60,11 +56,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public IActionResult Get(int id) { - if (!IsTokenValid("read:genre")) - { - return StatusCode(401, "Not allowed"); - } - var genre = new Genre { GenreID = id diff --git a/Controllers/v1/LoginController.cs b/Controllers/v1/LoginController.cs index 881e3d9..0458b4c 100644 --- a/Controllers/v1/LoginController.cs +++ b/Controllers/v1/LoginController.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Configuration; using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -75,7 +72,6 @@ namespace Icarus.Controllers.V1 TokenManager tk = new TokenManager(_config); - // loginRes = tk.LogIn(user); loginRes = tk.LoginSymmetric(user); return Ok(loginRes); diff --git a/Controllers/v1/SongCompressedDataControllers.cs b/Controllers/v1/SongCompressedDataControllers.cs index cc96800..6045caf 100644 --- a/Controllers/v1/SongCompressedDataControllers.cs +++ b/Controllers/v1/SongCompressedDataControllers.cs @@ -19,6 +19,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/song/compressed/data")] [ApiController] + [Authorize] public class SongCompressedDataController : BaseController { #region Fields @@ -47,11 +48,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public async Task Get(int id) { - if (!IsTokenValid("download:songs")) - { - return StatusCode(401, "Not allowed"); - } - var context = new SongContext(_connectionString); SongCompression cmp = new SongCompression(_archiveDir); diff --git a/Controllers/v1/SongController.cs b/Controllers/v1/SongController.cs index d94f78f..05cc9f2 100644 --- a/Controllers/v1/SongController.cs +++ b/Controllers/v1/SongController.cs @@ -18,6 +18,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/song")] [ApiController] + [Authorize] public class SongController : BaseController { #region Fields @@ -49,11 +50,6 @@ namespace Icarus.Controllers.V1 [HttpGet] public IActionResult Get() { - if (!IsTokenValid("read:song_details")) - { - return StatusCode(401, "Not allowed"); - } - List songs = new List(); Console.WriteLine("Attemtping to retrieve songs"); _logger.LogInformation("Attempting to retrieve songs"); @@ -71,11 +67,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public IActionResult Get(int id) { - if (!IsTokenValid("read:song_details")) - { - return StatusCode(401, "Not allowed"); - } - var context = new SongContext(_connectionString); Song song = new Song { SongID = id }; @@ -92,11 +83,6 @@ namespace Icarus.Controllers.V1 [HttpPut("{id}")] public IActionResult Put(int id, [FromBody] Song song) { - if (!IsTokenValid("update:songs")) - { - return StatusCode(401, "Not allowed"); - } - var context = new SongContext(_connectionString); song.SongID = id; diff --git a/Controllers/v1/SongDataController.cs b/Controllers/v1/SongDataController.cs index 4760939..ab64a50 100644 --- a/Controllers/v1/SongDataController.cs +++ b/Controllers/v1/SongDataController.cs @@ -20,6 +20,7 @@ namespace Icarus.Controllers.V1 { [Route("api/v1/song/data")] [ApiController] + [Authorize] public class SongDataController : BaseController { #region Fields @@ -50,11 +51,6 @@ namespace Icarus.Controllers.V1 [Route("private-scoped")] public async Task Get(int id) { - if (!IsTokenValid("download:songs")) - { - return StatusCode(401, "Not allowed"); - } - var songContext = new SongContext(_connectionString); var songMetaData = songContext.RetrieveRecord(new Song { SongID = id}); @@ -79,11 +75,6 @@ namespace Icarus.Controllers.V1 [Route("private-scoped")] public IActionResult Post([FromForm(Name = "file")] List songData) { - if (!IsTokenValid("upload:songs")) - { - return StatusCode(401, "Not allowed"); - } - try { // Console.WriteLine("Uploading song..."); @@ -121,11 +112,6 @@ namespace Icarus.Controllers.V1 [Route("private-scoped")] public IActionResult Post ([FromForm] UploadSongWithDataForm up) { - if (!IsTokenValid("upload:songs")) - { - return StatusCode(401, "Not allowed"); - } - try { if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile)) @@ -147,11 +133,6 @@ namespace Icarus.Controllers.V1 [HttpDelete("delete/{id}")] public IActionResult Delete(int id) { - if (!IsTokenValid("delete:songs")) - { - return StatusCode(401, "Not allowed"); - } - var songContext = new SongContext(_connectionString); var songMetaData = new Song{ SongID = id }; diff --git a/Controllers/v1/SongStreamController.cs b/Controllers/v1/SongStreamController.cs index 4479800..967a74b 100644 --- a/Controllers/v1/SongStreamController.cs +++ b/Controllers/v1/SongStreamController.cs @@ -47,13 +47,6 @@ namespace Icarus.Controllers.V1 [HttpGet("{id}")] public async Task Get(int id) { - /** - if (!IsTokenValid("stream:songs")) - { - return StatusCode(401, "Not allowed"); - } - */ - var context = new SongContext(_config.GetConnectionString("DefaultConnection")); var song = context.Songs.FirstOrDefault(sng => sng.SongID == id); diff --git a/Startup.cs b/Startup.cs index 7462694..88603bd 100644 --- a/Startup.cs +++ b/Startup.cs @@ -19,35 +19,6 @@ using Icarus.Database.Contexts; namespace Icarus { - public static class ServiceStartup - { - public static IServiceCollection AddAsymmetricAuthentication(this IServiceCollection services, IConfiguration configuration) - { - var issuerSigningCertificate = new Icarus.Certs.SigningIssuerCertificate(); - RsaSecurityKey issuerSigningKey = issuerSigningCertificate.GetIssuerSigningKey(configuration["RSAKeys:PublicKeyPath"]); - - services.AddAuthentication(authOptions => - { - }) - .AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - IssuerSigningKey = issuerSigningKey, - }; - }); - - return services; - } - - private static bool LifetimeValidator(DateTime? notBefore, - DateTime? expires, - SecurityToken securityToken, - TokenValidationParameters validationParameters) - { - return expires != null && expires > DateTime.UtcNow; - } - } public class Startup { #region Constructors @@ -95,16 +66,6 @@ namespace Icarus services.AddDbContext(options => options.UseMySQL(connString)); services.AddDbContext(options => options.UseMySQL(connString)); - // services.AddAsymmetricAuthentication(Configuration); - - /** - services.AddTransient(au => new AuthenticationService(new UserService(Configuration), new TokenService(Configuration), Configuration)); - services.AddTransient(us => new UserService(Configuration)); - services.AddTransient(tk => new TokenService(Configuration)); - services.AddTransient(); - services.AddTransient(uc => new UserContext(connString)); - */ - services.AddControllers() .AddNewtonsoftJson(); } -- 2.47.3 From 11fe1f29e1fbd9205a70aa0e3a6fd28567acf426 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 20:50:35 -0400 Subject: [PATCH 5/9] Updated Readme --- README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e24d59e..4062380 100644 --- a/README.md +++ b/README.md @@ -37,32 +37,27 @@ One can interface with Icarus the music server either by: ## Getting started There are several things that need to be completed to properly setup and secure the API. -1. Creating RSA keys +#### 1. Creating RSA keys +1. JWT Information 2. API filesystem paths 3. Database connection string 4. Migrations -### Creating RSA keys +### JWT Information -1. Create private key -``` -openssl genrsa -out private.pem 2048 -``` -2. Create public key -``` -openssl rsa -in private -pubout -out public.pem -``` - -Configure the key paths in the config files +Configure JWT information. Notably the Secret ```Json -"RSAKeys": { - "PrivateKeyPath": "", - "PublicKeyPath": "" -} + "JWT": { + "Issuer": "IcarusAPI", + "Audience": "IcarusAPIClient", + "Secret": "X1I9TbwaBG3RiwLiCJ69lHGxLFoNODDE", + "Subject": "Authorization" + }, ``` + Replace [domain] with the domain name that represent's your domain. Replace [identifier] with the identifer root name in the appsettings environment file. Not the friendly name but the root name of the identifier, omitting the http protocol and the *api* path. ```Json @@ -72,6 +67,9 @@ Replace [domain] with the domain name that represent's your domain. Replace [ide }, ``` +**Note**: The Auth0 section is likely to be changed or removed in future releases. + + ### API filesystem paths For the purposes of properly uploading, downloading, updating, deleting, and streaming songs the API filesystem paths must be configured. What is meant by this is that the `RootMusicPath` directory where all music will be stored must exist as well as the `ArchivePath` and `TemporaryMusicPath` paths. An example on a Linux system: -- 2.47.3 From 664b0d520b91314a81b5bf7de380e625919e8ec1 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sun, 4 Sep 2022 20:56:37 -0400 Subject: [PATCH 6/9] Updated Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4062380..d57c2e5 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Configure JWT information. Notably the Secret "JWT": { "Issuer": "IcarusAPI", "Audience": "IcarusAPIClient", - "Secret": "X1I9TbwaBG3RiwLiCJ69lHGxLFoNODDE", + "Secret": "Manaiswhatyouthinkitis", "Subject": "Authorization" }, ``` -- 2.47.3 From 38c056cc9961f5f9a02e05fb870bb984e75cb545 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Mon, 5 Sep 2022 16:58:10 -0400 Subject: [PATCH 7/9] Swagger docs --- Controllers/v1/AlbumController.cs | 4 +-- Controllers/v1/ArtistController.cs | 4 +-- Controllers/v1/BaseController.cs | 2 ++ Controllers/v1/CoverArtController.cs | 7 +++-- Controllers/v1/GenreController.cs | 4 +-- Controllers/v1/LoginController.cs | 3 +- Controllers/v1/RegisterController.cs | 2 +- Controllers/v1/SongController.cs | 6 ++-- Controllers/v1/SongDataController.cs | 38 +++++++++++++------------ Controllers/v1/SongStreamController.cs | 3 +- Icarus.csproj | 1 + Properties/launchSettings.json | 5 ++-- Startup.cs | 39 ++++++++++++++++++++++++++ 13 files changed, 83 insertions(+), 35 deletions(-) diff --git a/Controllers/v1/AlbumController.cs b/Controllers/v1/AlbumController.cs index 8a3a259..7980f71 100644 --- a/Controllers/v1/AlbumController.cs +++ b/Controllers/v1/AlbumController.cs @@ -39,7 +39,7 @@ namespace Icarus.Controllers.V1 #region HTTP Routes [HttpGet] - public IActionResult Get() + public IActionResult GetAlbums() { List albums = new List(); @@ -54,7 +54,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult Get(int id) + public IActionResult GetAlbum(int id) { Album album = new Album { diff --git a/Controllers/v1/ArtistController.cs b/Controllers/v1/ArtistController.cs index a27f48a..219e6c5 100644 --- a/Controllers/v1/ArtistController.cs +++ b/Controllers/v1/ArtistController.cs @@ -38,7 +38,7 @@ namespace Icarus.Controllers.V1 #region HTTP Routes [HttpGet] - public IActionResult Get() + public IActionResult GetArtists() { var artistContext = new ArtistContext(_connectionString); @@ -51,7 +51,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult Get(int id) + public IActionResult GetArtist(int id) { if (!IsTokenValid("read:artists")) { diff --git a/Controllers/v1/BaseController.cs b/Controllers/v1/BaseController.cs index 911d273..9cec68a 100644 --- a/Controllers/v1/BaseController.cs +++ b/Controllers/v1/BaseController.cs @@ -17,6 +17,7 @@ namespace Icarus.Controllers.V1 #region Methods + [ApiExplorerSettings(IgnoreApi = true)] protected string ParseBearerTokenFromHeader() { var token = string.Empty; @@ -37,6 +38,7 @@ namespace Icarus.Controllers.V1 return token; } + [ApiExplorerSettings(IgnoreApi = true)] protected bool IsTokenValid(string scope) { var token = ParseBearerTokenFromHeader(); diff --git a/Controllers/v1/CoverArtController.cs b/Controllers/v1/CoverArtController.cs index 2dd422a..4d3ef29 100644 --- a/Controllers/v1/CoverArtController.cs +++ b/Controllers/v1/CoverArtController.cs @@ -35,7 +35,8 @@ namespace Icarus.Controllers.V1 #region HTTP Routes - public IActionResult Get() + [HttpGet] + public IActionResult GetCoverArts() { var coverArtContext = new CoverArtContext(_connectionString); @@ -54,7 +55,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public async Task Get(int id) + public IActionResult GetCoverArt(int id) { var coverArt = new CoverArt { CoverArtID = id }; @@ -65,7 +66,7 @@ namespace Icarus.Controllers.V1 if (coverArt != null) { _logger.LogInformation("Found cover art record"); - var coverArtBytes = await System.IO.File.ReadAllBytesAsync( + var coverArtBytes = System.IO.File.ReadAllBytes( coverArt.ImagePath); return File(coverArtBytes, "application/x-msdownload", diff --git a/Controllers/v1/GenreController.cs b/Controllers/v1/GenreController.cs index ae553e6..8fbd4ff 100644 --- a/Controllers/v1/GenreController.cs +++ b/Controllers/v1/GenreController.cs @@ -39,7 +39,7 @@ namespace Icarus.Controllers.V1 #region HTTP Routes [HttpGet] - public IActionResult Get() + public IActionResult RetrieveGenres() { var genres = new List(); @@ -54,7 +54,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult Get(int id) + public IActionResult RetrieveGenre(int id) { var genre = new Genre { diff --git a/Controllers/v1/LoginController.cs b/Controllers/v1/LoginController.cs index 0458b4c..8446063 100644 --- a/Controllers/v1/LoginController.cs +++ b/Controllers/v1/LoginController.cs @@ -38,7 +38,8 @@ namespace Icarus.Controllers.V1 #region HTTP endpoints - public IActionResult Post([FromBody] User user) + [HttpPost] + public IActionResult Login([FromBody] User user) { var context = new UserContext(_connectionString); diff --git a/Controllers/v1/RegisterController.cs b/Controllers/v1/RegisterController.cs index e1352d8..202b728 100644 --- a/Controllers/v1/RegisterController.cs +++ b/Controllers/v1/RegisterController.cs @@ -37,7 +37,7 @@ namespace Icarus.Controllers.V1 #endregion [HttpPost] - public IActionResult Post([FromBody] User user) + public IActionResult RegisterUser([FromBody] User user) { PasswordEncryption pe = new PasswordEncryption(); user.Password = pe.HashPassword(user); diff --git a/Controllers/v1/SongController.cs b/Controllers/v1/SongController.cs index 05cc9f2..39be0cf 100644 --- a/Controllers/v1/SongController.cs +++ b/Controllers/v1/SongController.cs @@ -48,7 +48,7 @@ namespace Icarus.Controllers.V1 [HttpGet] - public IActionResult Get() + public IActionResult RetrieveSongs() { List songs = new List(); Console.WriteLine("Attemtping to retrieve songs"); @@ -65,7 +65,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult Get(int id) + public IActionResult RetrieveSong(int id) { var context = new SongContext(_connectionString); @@ -81,7 +81,7 @@ namespace Icarus.Controllers.V1 } [HttpPut("{id}")] - public IActionResult Put(int id, [FromBody] Song song) + public IActionResult UpdateSong(int id, [FromBody] Song song) { var context = new SongContext(_connectionString); diff --git a/Controllers/v1/SongDataController.cs b/Controllers/v1/SongDataController.cs index ab64a50..f459ef0 100644 --- a/Controllers/v1/SongDataController.cs +++ b/Controllers/v1/SongDataController.cs @@ -48,13 +48,13 @@ namespace Icarus.Controllers.V1 [HttpGet("download/{id}")] - [Route("private-scoped")] - public async Task Get(int id) + // [Route("private-scoped")] + public IActionResult Download(int id) { var songContext = new SongContext(_connectionString); var songMetaData = songContext.RetrieveRecord(new Song { SongID = id}); - var song = await _songMgr.RetrieveSong(songMetaData); + var song = _songMgr.RetrieveSong(songMetaData).Result; return File(song.Data, "application/x-msdownload", songMetaData.Filename); } @@ -72,8 +72,9 @@ namespace Icarus.Controllers.V1 // Cover art // [HttpPost("upload"), DisableRequestSizeLimit] - [Route("private-scoped")] - public IActionResult Post([FromForm(Name = "file")] List songData) + // [ConflictingActionsResolver] + // [Route("private-scoped")] + public IActionResult Upload([FromForm(Name = "file")] List songData) { try { @@ -109,8 +110,9 @@ namespace Icarus.Controllers.V1 // as well as the cover art // [HttpPost("upload/with/data")] - [Route("private-scoped")] - public IActionResult Post ([FromForm] UploadSongWithDataForm up) + // [ConflictingActionsResolver] + // [Route("private-scoped")] + public IActionResult UploadWithData([FromForm] UploadSongWithDataForm up) { try { @@ -131,7 +133,7 @@ namespace Icarus.Controllers.V1 } [HttpDelete("delete/{id}")] - public IActionResult Delete(int id) + public IActionResult DeleteSong(int id) { var songContext = new SongContext(_connectionString); @@ -156,15 +158,15 @@ namespace Icarus.Controllers.V1 } - public class UploadSongWithDataForm - { - [FromForm(Name = "file")] - public IFormFile SongData { get; set; } - // TODO: Think about making this optional and if it is not provided, use the stock cover art - [FromForm(Name = "cover")] - public IFormFile CoverArtData { get; set; } - [FromForm(Name = "metadata")] - public string SongFile { get; set; } - } + public class UploadSongWithDataForm + { + [FromForm(Name = "file")] + public IFormFile SongData { get; set; } + // TODO: Think about making this optional and if it is not provided, use the stock cover art + [FromForm(Name = "cover")] + public IFormFile CoverArtData { get; set; } + [FromForm(Name = "metadata")] + public string SongFile { get; set; } + } } } diff --git a/Controllers/v1/SongStreamController.cs b/Controllers/v1/SongStreamController.cs index 967a74b..ad8ef4d 100644 --- a/Controllers/v1/SongStreamController.cs +++ b/Controllers/v1/SongStreamController.cs @@ -44,8 +44,9 @@ namespace Icarus.Controllers.V1 #region HTTP endpoints + // [HttpGet] [HttpGet("{id}")] - public async Task Get(int id) + public async Task StreamSong(int id) { var context = new SongContext(_config.GetConnectionString("DefaultConnection")); diff --git a/Icarus.csproj b/Icarus.csproj index 628bbcb..7e626cd 100644 --- a/Icarus.csproj +++ b/Icarus.csproj @@ -25,6 +25,7 @@ + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 82a1227..b71d9cb 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -12,7 +12,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "api/values", + "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -20,7 +20,8 @@ "Icarus": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/values", + "dotnetRuneMessage": true, + "launchUrl": "swagger", "applicationUrl": "http://localhost:5002", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Startup.cs b/Startup.cs index 88603bd..89c50d1 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,15 +1,18 @@ using System; using System.Text; +using System.Linq; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using Newtonsoft.Json; using NLog; using NLog.Web; @@ -68,12 +71,48 @@ namespace Icarus services.AddControllers() .AddNewtonsoftJson(); + + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(c => + { + c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Icarus", Version = "v1" }); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Name = "Authorization", + Scheme = "Bearer", + BearerFormat = "JWT", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Description = "Bearer *Auth Token*", + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); + }); } // Called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // NOTE: Dev-related configuration can be done when env.IsDevelopment() evaluated to true + if (env.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } app.UseRouting(); app.UseAuthentication(); -- 2.47.3 From 73bfff39400b56dba5ad9f49e59e1116053e273c Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Mon, 5 Sep 2022 17:02:25 -0400 Subject: [PATCH 8/9] Cleanup --- Controllers/v1/GenreController.cs | 4 ++-- Controllers/v1/SongCompressedDataControllers.cs | 2 +- Controllers/v1/SongController.cs | 4 ++-- Controllers/v1/SongDataController.cs | 7 +------ Controllers/v1/SongStreamController.cs | 1 - 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Controllers/v1/GenreController.cs b/Controllers/v1/GenreController.cs index 8fbd4ff..d5a86c1 100644 --- a/Controllers/v1/GenreController.cs +++ b/Controllers/v1/GenreController.cs @@ -39,7 +39,7 @@ namespace Icarus.Controllers.V1 #region HTTP Routes [HttpGet] - public IActionResult RetrieveGenres() + public IActionResult GetGenres() { var genres = new List(); @@ -54,7 +54,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult RetrieveGenre(int id) + public IActionResult GetGenre(int id) { var genre = new Genre { diff --git a/Controllers/v1/SongCompressedDataControllers.cs b/Controllers/v1/SongCompressedDataControllers.cs index 6045caf..789a5d4 100644 --- a/Controllers/v1/SongCompressedDataControllers.cs +++ b/Controllers/v1/SongCompressedDataControllers.cs @@ -46,7 +46,7 @@ namespace Icarus.Controllers.V1 #region API Routes [HttpGet("{id}")] - public async Task Get(int id) + public async Task DownloadCompressedSong(int id) { var context = new SongContext(_connectionString); diff --git a/Controllers/v1/SongController.cs b/Controllers/v1/SongController.cs index 39be0cf..c7ebe3d 100644 --- a/Controllers/v1/SongController.cs +++ b/Controllers/v1/SongController.cs @@ -48,7 +48,7 @@ namespace Icarus.Controllers.V1 [HttpGet] - public IActionResult RetrieveSongs() + public IActionResult GetSongs() { List songs = new List(); Console.WriteLine("Attemtping to retrieve songs"); @@ -65,7 +65,7 @@ namespace Icarus.Controllers.V1 } [HttpGet("{id}")] - public IActionResult RetrieveSong(int id) + public IActionResult GetSong(int id) { var context = new SongContext(_connectionString); diff --git a/Controllers/v1/SongDataController.cs b/Controllers/v1/SongDataController.cs index f459ef0..48341b6 100644 --- a/Controllers/v1/SongDataController.cs +++ b/Controllers/v1/SongDataController.cs @@ -48,7 +48,6 @@ namespace Icarus.Controllers.V1 [HttpGet("download/{id}")] - // [Route("private-scoped")] public IActionResult Download(int id) { var songContext = new SongContext(_connectionString); @@ -72,8 +71,6 @@ namespace Icarus.Controllers.V1 // Cover art // [HttpPost("upload"), DisableRequestSizeLimit] - // [ConflictingActionsResolver] - // [Route("private-scoped")] public IActionResult Upload([FromForm(Name = "file")] List songData) { try @@ -110,8 +107,6 @@ namespace Icarus.Controllers.V1 // as well as the cover art // [HttpPost("upload/with/data")] - // [ConflictingActionsResolver] - // [Route("private-scoped")] public IActionResult UploadWithData([FromForm] UploadSongWithDataForm up) { try @@ -162,7 +157,7 @@ namespace Icarus.Controllers.V1 { [FromForm(Name = "file")] public IFormFile SongData { get; set; } - // TODO: Think about making this optional and if it is not provided, use the stock cover art + // NOTE: Think about making this optional and if it is not provided, use the stock cover art [FromForm(Name = "cover")] public IFormFile CoverArtData { get; set; } [FromForm(Name = "metadata")] diff --git a/Controllers/v1/SongStreamController.cs b/Controllers/v1/SongStreamController.cs index ad8ef4d..de92076 100644 --- a/Controllers/v1/SongStreamController.cs +++ b/Controllers/v1/SongStreamController.cs @@ -44,7 +44,6 @@ namespace Icarus.Controllers.V1 #region HTTP endpoints - // [HttpGet] [HttpGet("{id}")] public async Task StreamSong(int id) { -- 2.47.3 From c191d7ee7a88fd066b16dd210d7aec4d8b7675d5 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Mon, 5 Sep 2022 17:07:26 -0400 Subject: [PATCH 9/9] Update README.md Note about swagger --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d57c2e5..c3bb084 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,9 @@ One can interface with Icarus the music server either by: ## Getting started There are several things that need to be completed to properly setup and secure the API. -#### 1. Creating RSA keys +This API uses OpenAPI Specification 3.0. After configuring the API, launch the software +and navigate your browser to https://localhost:5001/swagger to view the endpoints. + 1. JWT Information 2. API filesystem paths 3. Database connection string @@ -138,11 +140,6 @@ From this point the database has been successfully configured. Metadata and song Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests to the project. -## Authors - -* [kdeng00](https://github.com/kdeng00) - -See also the list of [contributors](https://github.com/amazing-username/Icarus/graphs/contributors) who participated in this project. ## License -- 2.47.3