case 4711 - JWT signing key generated by the server; AYANOVA_JWT_SECRET removed

The LAN/IIS and Linux templates shipped a placeholder secret printed in the
manual, and installs that kept it signed logins with a public key; any setting
also invited short or reused values (security review F1). The setting is now
ignored (one warning at boot if still present): the server generates a key once
(48 random bytes) into jwt-signing.key in the data folder (beside the app if
there is none; owner-only on Linux) and reuses it, so sign-ins also survive a
restart where the server used to make a new key each boot (standalone, Linux
desktop). If the file cannot be used, a temporary key is used for that run.

Templates and dev launch configs drop the setting; manual updated (key page
rewritten, setting steps removed from install pages); changelog line.

Checked locally: token forged with the old placeholder 401; key file created
and reused after restart (old token 200); auth and 2FA tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:01:08 -07:00
parent 5bf1f2700a
commit a843bd3132
14 changed files with 61 additions and 113 deletions

View File

@@ -59,7 +59,12 @@ namespace AyaNova.Util
internal static long AYANOVA_DEFAULT_TRANSLATION_ID { get; set; } //internal setting set at boot by TranslationBiz::ValidateTranslations
//API
//case 4711: the JWT signing key, set at boot from JWT_SIGNING_KEY_FILE_PATH (generated once); no longer a setting
internal static string AYANOVA_JWT_SECRET { get; set; }
//case 4711: true if the old AYANOVA_JWT_SECRET setting is still present, so boot can say it is ignored
internal static bool AYANOVA_JWT_SECRET_SETTING_PRESENT { get; set; }
//case 4711: where the generated JWT signing key is kept (not a setting, derived from AYANOVA_DATA_PATH, else beside the app)
internal static string JWT_SIGNING_KEY_FILE_PATH { get; set; }
internal static string AYANOVA_USE_URLS { get; set; }
internal static int AYANOVA_REPORT_RENDERING_TIMEOUT { get; set; }
@@ -174,7 +179,8 @@ namespace AyaNova.Util
? "http://*:7575"
: AYANOVA_USE_URLS;
AYANOVA_JWT_SECRET = config.GetValue<string>("AYANOVA_JWT_SECRET");
//case 4711: no longer used, only noted so boot can log that it is ignored
AYANOVA_JWT_SECRET_SETTING_PRESENT = !string.IsNullOrWhiteSpace(config.GetValue<string>("AYANOVA_JWT_SECRET"));
//backdoor back door password superuser reset
AYANOVA_SET_SUPERUSER_PW = config.GetValue<string>("AYANOVA_SET_SUPERUSER_PW");
@@ -270,6 +276,10 @@ namespace AyaNova.Util
(string.IsNullOrWhiteSpace(DataFolderPath))
? string.Empty
: Path.Combine(DataFolderPath, "report-browser");
JWT_SIGNING_KEY_FILE_PATH =
(string.IsNullOrWhiteSpace(DataFolderPath))
? Path.Combine(AppContext.BaseDirectory, "jwt-signing.key")
: Path.Combine(DataFolderPath, "jwt-signing.key");
#endregion server BASICS
}