case 4725 - sessions end on password reset and login disabled, exact login route, default SuperUser limited when licensed

A password set by someone else (reset code, administrator) or a disabled
login replaces the user's session token and download key; changing one's
own password keeps the session. PasswordResetCodeExpire is cleared (was
DlKeyExpire). The token middleware exempts only the exact login and
notify/hello routes and refuses inactive users and users who may not log
in. With a purchased licence, notify/hello no longer offers the default
SuperUser login and a SuperUser with the default password may only change
it (SuperUserDefaultPasswordRoutes). F7, F8 and F9 fixed. Manual and
changelog updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 19:54:03 -07:00
parent 4141217c04
commit 80f81aea0f
12 changed files with 123 additions and 22 deletions

View File

@@ -405,7 +405,7 @@ namespace AyaNova
var ct = context.RequestServices.GetService<AyContext>();
//get the user record
var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, m.UserType, id = m.Id, translationId = m.UserOptions.TranslationId, currentAuthToken = m.CurrentAuthToken }).FirstAsync();
var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, m.UserType, id = m.Id, translationId = m.UserOptions.TranslationId, currentAuthToken = m.CurrentAuthToken, m.Active, m.AllowLogin, m.Login, m.Salt, m.Password }).FirstAsync();
//case 4713: a customer contact acts only with the customer roles, whatever roles are stored
context.Request.HttpContext.Items["AY_ROLES"] = UserBiz.EffectiveRoles(u.UserType, u.roles);
context.Request.HttpContext.Items["AY_USERNAME"] = u.name;
@@ -422,10 +422,13 @@ namespace AyaNova
//CHECK JWT
//case 4725: the login and prelogin routes are matched exactly (was any path ending in "/auth" or "notify/hello"),
//and a user who is inactive or may not log in is refused like a replaced token
var routeKey = AyaNova.Api.ControllerHelpers.CustomerContactRoutes.RouteKey(context);
if (
!context.Request.Path.Value.EndsWith("/auth") &&
!context.Request.Path.Value.EndsWith("notify/hello") &&
u.currentAuthToken != JWT
routeKey != "POST auth" &&
routeKey != "GET notify/hello" &&
(u.currentAuthToken != JWT || !u.Active || !u.AllowLogin)
)//except "/api/v8/auth" and prelogin notify/hello routes so user can login
{
@@ -460,7 +463,10 @@ namespace AyaNova
else
{
//case 4712: customer contacts may only call the routes the customer screens use
if ((u.UserType == UserType.Customer || u.UserType == UserType.HeadOffice) && !AyaNova.Api.ControllerHelpers.CustomerContactRoutes.IsAllowed(context))
//case 4725: with a purchased licence, a SuperUser with the default password may only change it
if (((u.UserType == UserType.Customer || u.UserType == UserType.HeadOffice) && !AyaNova.Api.ControllerHelpers.CustomerContactRoutes.IsAllowed(context)) ||
(u.id == 1 && AyaNova.Core.License.PurchasedLicense() && UserBiz.IsDefaultSuperUserCreds(u.Login, u.Salt, u.Password) &&
!AyaNova.Api.ControllerHelpers.SuperUserDefaultPasswordRoutes.IsAllowed(context)))
{
context.Response.StatusCode = 403;
context.Response.ContentType = "application/json";