Fix client base path handling via explicit meta
This commit is contained in:
3
IIS.md
3
IIS.md
@@ -12,9 +12,10 @@
|
|||||||
- Set environment variables in web.config or IIS config:
|
- Set environment variables in web.config or IIS config:
|
||||||
- `ASPNETCORE_ENVIRONMENT=Production`
|
- `ASPNETCORE_ENVIRONMENT=Production`
|
||||||
- `ADMIN_PASSWORD=<your-secret>`
|
- `ADMIN_PASSWORD=<your-secret>`
|
||||||
- `BasePath=/vote` (only if the site is under a subfolder; omit for root)
|
- `BasePath=/picknplay` (only if the site is under a subfolder; omit for root)
|
||||||
- Optional: enable stdout logging in `web.config` during troubleshooting only; disable afterward.
|
- Optional: enable stdout logging in `web.config` during troubleshooting only; disable afterward.
|
||||||
- Data protection keys are persisted to `App_Data/keys`; ensure this folder is deployed and writable so auth cookies stay valid across app pool recycles.
|
- Data protection keys are persisted to `App_Data/keys`; ensure this folder is deployed and writable so auth cookies stay valid across app pool recycles.
|
||||||
|
- Frontend base path: set `<meta name="app-base" content="/picknplay">` in `wwwroot/index.html` for production so API calls include the subpath (keep blank for local/root).
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
- Grant modify rights to the app pool identity on `App_Data` (DB file + wal).
|
- Grant modify rights to the app pool identity on `App_Data` (DB file + wal).
|
||||||
|
|||||||
2
TASKS.md
2
TASKS.md
@@ -5,6 +5,6 @@
|
|||||||
- [ ] **High - SSRF surface in screenshot validation** (`Endpoints/EndpointHelpers.IsReachableImageAsync`, `SuggestEndpoints.cs`): The server performs HEAD/GET requests to arbitrary user-provided URLs with minimal limits, enabling internal port probing and latency spikes. Restrict to allowlisted hosts or disable remote fetch; at minimum block private IPs/localhost, disallow redirects, and enforce tight size/time limits.
|
- [ ] **High - SSRF surface in screenshot validation** (`Endpoints/EndpointHelpers.IsReachableImageAsync`, `SuggestEndpoints.cs`): The server performs HEAD/GET requests to arbitrary user-provided URLs with minimal limits, enabling internal port probing and latency spikes. Restrict to allowlisted hosts or disable remote fetch; at minimum block private IPs/localhost, disallow redirects, and enforce tight size/time limits.
|
||||||
- [ ] **High - Phase recalculation writes on every poll** (`Endpoints/EndpointHelpers.GetPhase`, `wwwroot/app.js`): `GetPhase` unconditionally calls `SaveChangesAsync`; the client polls `/api/state` every 4s, causing constant writes and SQLite WAL churn/locks even when nothing changes. Track a `changed` flag and persist only when state mutations occur; consider reducing poll frequency or using long polling/server push later.
|
- [ ] **High - Phase recalculation writes on every poll** (`Endpoints/EndpointHelpers.GetPhase`, `wwwroot/app.js`): `GetPhase` unconditionally calls `SaveChangesAsync`; the client polls `/api/state` every 4s, causing constant writes and SQLite WAL churn/locks even when nothing changes. Track a `changed` flag and persist only when state mutations occur; consider reducing poll frequency or using long polling/server push later.
|
||||||
- [ ] **Medium - Admin key accepted via query string** (`Endpoints/EndpointHelpers.IsAdmin`): Accepting `key` in the query leaks secrets via logs and referrers. Require the `X-Admin-Key` header (or authenticated admin account) only, and rate-limit/monitor attempts.
|
- [ ] **Medium - Admin key accepted via query string** (`Endpoints/EndpointHelpers.IsAdmin`): Accepting `key` in the query leaks secrets via logs and referrers. Require the `X-Admin-Key` header (or authenticated admin account) only, and rate-limit/monitor attempts.
|
||||||
- [ ] **Medium - Brittle base path detection on the client** (`wwwroot/js/api.js`): The heuristic that picks the first path segment can double-prefix or break when the app is nested (e.g., `/apps/vote/`). Prefer explicit `<meta name="app-base">` or `<base>` and drop the autodetect fallback.
|
- [x] **Medium - Brittle base path detection on the client** (`wwwroot/js/api.js`): The heuristic that picks the first path segment can double-prefix or break when the app is nested (e.g., `/picknplay/`). Prefer explicit `<meta name="app-base">` or `<base>` and drop the autodetect fallback.
|
||||||
- [ ] **Maintenance - Centralize auth/phase concerns** (multiple endpoints): Each endpoint manually fetches the player, admin status, and phase gating, repeating logic and increasing drift risk. Introduce endpoint filters/middleware to attach the current player and enforce phase/admin requirements declaratively.
|
- [ ] **Maintenance - Centralize auth/phase concerns** (multiple endpoints): Each endpoint manually fetches the player, admin status, and phase gating, repeating logic and increasing drift risk. Introduce endpoint filters/middleware to attach the current player and enforce phase/admin requirements declaratively.
|
||||||
- [ ] **Maintenance - Clear invalid cookies** (`Infrastructure/PlayerIdentityExtensions.cs`): When a cookie references a deleted player, we keep reissuing it; endpoints respond 401 but the cookie persists. Clear the cookie if lookup fails to avoid client loops.
|
- [ ] **Maintenance - Clear invalid cookies** (`Infrastructure/PlayerIdentityExtensions.cs`): When a cookie references a deleted player, we keep reissuing it; endpoints respond 401 but the cookie persists. Clear the cookie if lookup fails to avoid client loops.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap" rel="stylesheet">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<!-- Optional: set data-app-base if served from a subfolder (e.g., /vote) -->
|
<!-- Set to "/picknplay" in production; leave blank for localhost/root -->
|
||||||
<meta name="app-base" content="">
|
<meta name="app-base" content="">
|
||||||
</head>
|
</head>
|
||||||
<body class="page">
|
<body class="page">
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
const defaultHeaders = { "Content-Type": "application/json" };
|
const defaultHeaders = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
const metaBase = document.querySelector('meta[name="app-base"]')?.content || "";
|
const rawBase = document.querySelector('meta[name="app-base"]')?.content || "";
|
||||||
const autoBase = (() => {
|
const basePath = normalizeBase(rawBase);
|
||||||
const parts = window.location.pathname.split("/").filter(Boolean);
|
|
||||||
return parts.length ? `/${parts[0]}` : "";
|
|
||||||
})();
|
|
||||||
const basePath = metaBase || autoBase;
|
|
||||||
const withBase = (path) => `${basePath}${path}`;
|
const withBase = (path) => `${basePath}${path}`;
|
||||||
|
|
||||||
|
function normalizeBase(value) {
|
||||||
|
if (!value) return "";
|
||||||
|
if (!value.startsWith("/")) return `/${value}`;
|
||||||
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
||||||
|
}
|
||||||
|
|
||||||
async function request(path, { method = "GET", body } = {}) {
|
async function request(path, { method = "GET", body } = {}) {
|
||||||
const res = await fetch(withBase(path), {
|
const res = await fetch(withBase(path), {
|
||||||
method,
|
method,
|
||||||
|
|||||||
Reference in New Issue
Block a user