From 8c19e087e2e4e5070167f846bc335116eb21584c Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 21 Apr 2020 18:32:31 +0000 Subject: [PATCH] --- devdocs/todo.txt | 4 + .../Controllers/AttachmentController.cs | 93 ++++++++++--------- server/AyaNova/Controllers/AuthController.cs | 33 +++++-- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/devdocs/todo.txt b/devdocs/todo.txt index 72a20aa2..70e16496 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -10,6 +10,10 @@ todo: check attachment NOTES property is actually supported //todo: search tables in schema, I think there is a missing index here, need to look at the search query section again as it was changed several times from the original schema creation + +todo: can a user be locked out from the server end even though they posess a valid token? + - and prevent download of images etc? + todo: api / server landing page is shitty on a mobile todo: add query fail logging to datalist just like done with picklist so in production can catch mysterious problems more easily diff --git a/server/AyaNova/Controllers/AttachmentController.cs b/server/AyaNova/Controllers/AttachmentController.cs index 50e5fb44..46d2580b 100644 --- a/server/AyaNova/Controllers/AttachmentController.cs +++ b/server/AyaNova/Controllers/AttachmentController.cs @@ -60,55 +60,54 @@ namespace AyaNova.Api.Controllers + //Moved this functionality to authentication and expiry follows jwt token expiry + // //LOOKAT: Centralize this code somewhere else, it's going to be needed for backup as well + // //consider the 1 hour thing, is this legit depending on client? + // /// + // /// Get download token + // /// A download token is good for 1 hour from issue + // /// + // /// Current download token for user + // [HttpGet("DownloadToken")] + // public async Task GetDownloadTokenAsync() + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - //LOOKAT: Centralize this code somewhere else, it's going to be needed for backup as well - //consider the 1 hour thing, is this legit depending on client? + // long lUserId = UserIdFromContext.Id(HttpContext.Items); + // var u = await ct.User.FirstOrDefaultAsync(a => a.Id == lUserId); + // if (u == null) + // return NotFound(); + // else + // { - /// - /// Get download token - /// A download token is good for 1 hour from issue - /// - /// Current download token for user - [HttpGet("DownloadToken")] - public async Task GetDownloadTokenAsync() - { - if (!serverState.IsOpen) - return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // //Generate a download token and store it with the user account + // //users who are authenticated can get their token via download route + // Guid g = Guid.NewGuid(); + // string dlkey = Convert.ToBase64String(g.ToByteArray()); + // dlkey = dlkey.Replace("=", ""); + // dlkey = dlkey.Replace("+", ""); - long lUserId = UserIdFromContext.Id(HttpContext.Items); - var u = await ct.User.FirstOrDefaultAsync(a => a.Id == lUserId); - if (u == null) - return NotFound(); - else - { + // //get expiry date for download token + // var exp = new DateTimeOffset(DateTime.Now.AddHours(1).ToUniversalTime(), TimeSpan.Zero); - //Generate a download token and store it with the user account - //users who are authenticated can get their token via download route - Guid g = Guid.NewGuid(); - string dlkey = Convert.ToBase64String(g.ToByteArray()); - dlkey = dlkey.Replace("=", ""); - dlkey = dlkey.Replace("+", ""); + // u.DlKey = dlkey; + // u.DlKeyExpire = exp.DateTime; + // ct.User.Update(u); + // try + // { + // await ct.SaveChangesAsync();//triggering concurrency exception here + // } + // catch (Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException) + // { + // log.LogInformation("Auth retry dlkey"); + // }; - //get expiry date for download token - var exp = new DateTimeOffset(DateTime.Now.AddHours(1).ToUniversalTime(), TimeSpan.Zero); + // return Ok(ApiOkResponse.Response(new { dlkey = u.DlKey, expires = u.DlKeyExpire }, true)); + // } - u.DlKey = dlkey; - u.DlKeyExpire = exp.DateTime; - ct.User.Update(u); - try - { - await ct.SaveChangesAsync();//triggering concurrency exception here - } - catch (Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException) - { - log.LogInformation("Auth retry dlkey"); - }; - - return Ok(ApiOkResponse.Response(new { dlkey = u.DlKey, expires = u.DlKeyExpire }, true)); - } - - } + // } @@ -336,21 +335,25 @@ namespace AyaNova.Api.Controllers var dlkeyUser = await ct.User.SingleOrDefaultAsync(m => m.DlKey == dlkey); if (dlkeyUser == null) { - return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid")); + //don't want to leak information so just say not found + //return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid")); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } //Make sure the token provided is for the current user long UserId = UserIdFromContext.Id(HttpContext.Items); if (UserId != dlkeyUser.Id) { - return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid")); + // return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid")); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); if (dlkeyUser.DlKeyExpire < utcNow.DateTime) { - return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token has expired")); + // return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token has expired")); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } //Ok, user has a valid download key and it's not expired yet so get the attachment record diff --git a/server/AyaNova/Controllers/AuthController.cs b/server/AyaNova/Controllers/AuthController.cs index 53dc071b..f45229ae 100644 --- a/server/AyaNova/Controllers/AuthController.cs +++ b/server/AyaNova/Controllers/AuthController.cs @@ -158,7 +158,7 @@ namespace AyaNova.Api.Controllers //Multiple users are allowed the same password and login //Salt will differentiate them so get all users that match login, then try to match pw - var users = await ct.User.AsNoTracking().Where(m => m.Login == creds.Login).ToListAsync(); + var users = await ct.User.Where(m => m.Login == creds.Login && m.Active == true).ToListAsync(); foreach (User u in users) { @@ -177,13 +177,15 @@ namespace AyaNova.Api.Controllers } - //If the user is inactive they may not login - if (!u.Active) - { - //This is leaking information, instead just act like bad creds - //return StatusCode(401, new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "User deactivated")); - return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); - } + // //If the user is inactive they may not login + // if (!u.Active) + // { + // //This is leaking information, instead just act like bad creds + // //return StatusCode(401, new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "User deactivated")); + // return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + // } + + //build the key (JWT set in startup.cs) byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.AYANOVA_JWT_SECRET); @@ -192,6 +194,18 @@ namespace AyaNova.Api.Controllers var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);//timespan zero means zero time off utc / specifying this is a UTC datetime var exp = new DateTimeOffset(DateTime.Now.AddDays(JWT_LIFETIME_DAYS).ToUniversalTime(), TimeSpan.Zero); + + //=============== download token =================== + //Generate a download token and store it with the user account + //string DownloadToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); + string DownloadToken = Hasher.GenerateSalt(); + DownloadToken = DownloadToken.Replace("=", ""); + DownloadToken = DownloadToken.Replace("+", ""); + u.DlKey = DownloadToken; + u.DlKeyExpire = exp.DateTime; + await ct.SaveChangesAsync(); + //======================================================= + var payload = new Dictionary() { { "iat", iat.ToUnixTimeSeconds().ToString() }, @@ -200,7 +214,8 @@ namespace AyaNova.Api.Controllers { "id", u.Id.ToString() }, { "name", u.Name}, { "usertype", u.UserType}, - { "ayanova/roles", ((int)u.Roles).ToString() } + { "ayanova/roles", ((int)u.Roles).ToString()}, + { "dlt", DownloadToken } };