diff --git a/server/AyaNova/Controllers/AuthController.cs b/server/AyaNova/Controllers/AuthController.cs index 6e1ed7df..241eab95 100644 --- a/server/AyaNova/Controllers/AuthController.cs +++ b/server/AyaNova/Controllers/AuthController.cs @@ -193,7 +193,7 @@ namespace AyaNova.Api.Controllers DownloadToken = DownloadToken.Replace("+", ""); u.DlKey = DownloadToken; u.DlKeyExpire = exp.DateTime; - await ct.SaveChangesAsync(); + //======================================================= var payload = new Dictionary() @@ -213,6 +213,10 @@ namespace AyaNova.Api.Controllers //issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it. string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256); + //save auth token to ensure single sign on only + u.CurrentAuthToken = token; + await ct.SaveChangesAsync(); + log.LogDebug($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok"); metrics.Measure.Meter.Mark(MetricsRegistry.SuccessfulLoginMeter); diff --git a/server/AyaNova/Startup.cs b/server/AyaNova/Startup.cs index 26d544ab..526b4e8a 100644 --- a/server/AyaNova/Startup.cs +++ b/server/AyaNova/Startup.cs @@ -360,30 +360,56 @@ namespace AyaNova app.UseAuthorization(); - //Custom middleware to get user roles and put them into the request so + //Custom middleware to ensure token still valid and to + //get user roles and put them into the request so //they can be authorized in routes. app.Use(async (context, next) => { if (!context.User.Identity.IsAuthenticated) { context.Request.HttpContext.Items["AY_ROLES"] = 0; + await next.Invoke(); } else { //Get user ID from claims long userId = Convert.ToInt64(context.User.FindFirst(c => c.Type == "id").Value); + //Get JWT + string JWT = string.Empty; + var AuthHeaders = context.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.Authorization]; + foreach (String s in AuthHeaders) + { + if (s.ToLowerInvariant().Contains("bearer")) + { + JWT = s; + break; + } + } + //Get the database context - var ct = context.RequestServices.GetService(); + var ct = context.RequestServices.GetService(); //get the user record - var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, id = m.Id, translationId = m.UserOptions.TranslationId }).FirstAsync(); + var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, id = m.Id, translationId = m.UserOptions.TranslationId, currentAuthToken = m.CurrentAuthToken }).FirstAsync(); context.Request.HttpContext.Items["AY_ROLES"] = u.roles; context.Request.HttpContext.Items["AY_USERNAME"] = u.name; context.Request.HttpContext.Items["AY_USER_ID"] = u.id; context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = u.translationId; + + //CHECK JWT + if (u.currentAuthToken != JWT) + { + context.Response.StatusCode = 401; + context.Response.Headers.Add("X-AyaNova-Authorization-Error", "Authorization token was replaced by more recent login"); + await context.Response.WriteAsync("Authorization token was replaced by more recent login"); + } + else + { + await next.Invoke(); + } } - await next.Invoke(); + }); #endregion diff --git a/server/AyaNova/models/User.cs b/server/AyaNova/models/User.cs index 2325fe52..310cbfc6 100644 --- a/server/AyaNova/models/User.cs +++ b/server/AyaNova/models/User.cs @@ -22,8 +22,8 @@ namespace AyaNova.Models public string Salt { get; set; } [Required] public AuthorizationRoles Roles { get; set; } - // [Required] - // public long TranslationId { get; set; } + [JsonIgnore] + public string CurrentAuthToken { get; set; } public string DlKey { get; set; } public DateTime? DlKeyExpire { get; set; } diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 32e50498..ce3959f1 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -22,7 +22,7 @@ namespace AyaNova.Util //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! private const int DESIRED_SCHEMA_LEVEL = 11; - internal const long EXPECTED_COLUMN_COUNT = 271; + internal const long EXPECTED_COLUMN_COUNT = 272; internal const long EXPECTED_INDEX_COUNT = 118; //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! @@ -170,7 +170,7 @@ namespace AyaNova.Util //Add user table await ExecQueryAsync("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, active bool not null, name varchar(255) not null unique, " + - "login text not null, password text not null, salt text not null, roles integer not null, " + + "login text not null, password text not null, salt text not null, roles integer not null, currentauthtoken text, " + "dlkey text, dlkeyexpire timestamp, usertype integer not null, employeenumber varchar(255), notes text, customerid bigint, " + "headofficeid bigint, subvendorid bigint, wiki text null, customfields text, tags varchar(255) ARRAY)");