This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user