Security pass (#11): no default JWT secret, CORS allow-list, Diwan system emails

- appsettings: drop upstream's public JWT secret; API refuses to start outside Development without one
- Cors:AllowedOrigins (compose: https://SITE_DOMAIN); unset = any origin for local dev
- default admin/system/bot emails @diwan.local instead of @ganjoor.net

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Anas Rashid 2026-10-05 00:26:01 +02:00
parent 68594d1697
commit 987f69704b
4 changed files with 19 additions and 6 deletions

View File

@ -45,6 +45,8 @@ docker compose logs -f api # wait for "Application started"
SQL Server needs about 2 GB of RAM. Use a plan with at least 4 GB in total.
Security defaults: the API refuses to start outside Development without `JWT_SECRET`; browsers may call the API only from `https://SITE_DOMAIN` (`Cors:AllowedOrigins`); public sign-up is off (`SIGNUP_ENABLED=False`) until SMTP (`SmptConfig__*`) is configured.
### Load the data
1. Open `https://SITE_DOMAIN/login` and sign in with `ADMIN_EMAIL` and the password **`Test!123`**. The first login creates the admin account with that fixed password (RSecurityBackend's default; upstream's guide is wrong about this). **Change it right away** in the user panel.

View File

@ -34,6 +34,7 @@ using RSecurityBackend.Services.Implementation;
using RSecurityBackend.Utilities;
using Swashbuckle.AspNetCore.Filters;
using System;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Text;
@ -51,6 +52,10 @@ namespace RMuseum
public IConfiguration Configuration { get; }
private string[] AllowedOrigins => (Configuration["Cors:AllowedOrigins"] ?? "")
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(o => o.TrimEnd('/')).ToArray();
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
@ -58,7 +63,9 @@ namespace RMuseum
services.AddCors(options =>
{
options.AddPolicy("DiwanCorsPolicy",
builder => builder.SetIsOriginAllowed(_ => true)
// diwan: Cors:AllowedOrigins (comma separated, e.g. https://diwan.example) restricts browsers;
// unset = any origin (local development)
builder => builder.SetIsOriginAllowed(origin => AllowedOrigins.Length == 0 || AllowedOrigins.Contains(origin.TrimEnd('/'), StringComparer.OrdinalIgnoreCase))
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("paging-headers", "audio-upload-enabled", "items-count")
@ -353,6 +360,9 @@ namespace RMuseum
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// diwan: never run outside Development with a missing JWT signing secret (upstream shipped a public default)
if (!env.IsDevelopment() && string.IsNullOrWhiteSpace(Configuration["Security:Secret"]))
throw new InvalidOperationException("Security:Secret is not set (env Security__Secret). Refusing to start.");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();

View File

@ -4,7 +4,7 @@
},
"RSecurityBackend": {
"ApplicationName": "Diwan",
"FirstUserEmail": "admin@ganjoor.net"
"FirstUserEmail": "admin@diwan.local"
},
"BackgroundTaskQueue": {
"MaxConcurrency": "1"
@ -27,7 +27,7 @@
}
},
"Security": {
"Secret": "k4Fy7pDsc0LgXQ8PCCfZtwmju5Ed8asc",
"Secret": "",
"DefaultTokenExpirationInSeconds": "3600",
"SessionIdleTimeoutInDays": "90"
},
@ -57,8 +57,8 @@
"ShowAccountInfo": "False"
},
"Diwan": {
"SystemEmail": "sys@ganjoor.net",
"DeleteUserEmail": "del@ganjoor.net",
"SystemEmail": "sys@diwan.local",
"DeleteUserEmail": "del@diwan.local",
"SitemapLocation": "C:\\inetpub\\diwan\\wwwroot\\sitemap.xml",
"GDBStorage": "D:\\My Documents\\My Web Design\\diwan\\www\\i\\android\\sgdb",
"GDBStorageImageSource": "D:\\My Documents\\My Web Design\\diwan\\www\\i\\android\\img",
@ -75,7 +75,7 @@
"RemoteUrl": "https://github.com/ganjoor/ganjoor-data.git",
"Branch": "main",
"CommitAuthorName": "Diwan Export Bot",
"CommitAuthorEmail": "bot@ganjoor.net",
"CommitAuthorEmail": "bot@diwan.local",
"PushEnabled": "False",
"GitUserName": "",
"GitToken": "",

View File

@ -25,6 +25,7 @@ services:
RSecurityBackend__ApplicationName: Diwan
RSecurityBackend__FirstUserEmail: ${ADMIN_EMAIL:?set in .env}
SignUp__Enabled: ${SIGNUP_ENABLED:-False}
Cors__AllowedOrigins: https://${SITE_DOMAIN} # only the site may call the API from browsers
WebServiceUrl: https://${API_DOMAIN}
# Linux paths for upstream's Windows defaults
PictureFileService__StoragePath: /data/museum