This commit is contained in:
2020-04-21 18:32:31 +00:00
parent fd56b763be
commit 8c19e087e2
3 changed files with 76 additions and 54 deletions

View File

@@ -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

View File

@@ -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?
// /// <summary>
// /// Get download token
// /// A download token is good for 1 hour from issue
// /// </summary>
// /// <returns>Current download token for user</returns>
// [HttpGet("DownloadToken")]
// public async Task<IActionResult> 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
// {
/// <summary>
/// Get download token
/// A download token is good for 1 hour from issue
/// </summary>
/// <returns>Current download token for user</returns>
[HttpGet("DownloadToken")]
public async Task<IActionResult> 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

View File

@@ -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<string, object>()
{
{ "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 }
};