diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html index 911b7806..61135ca0 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html @@ -1,9 +1,9 @@ @if (selectedGym(); as gym) {
@if (gym.url) { - + } @else { - + }
{{ gym.name ?? gym.id }} @@ -31,9 +31,9 @@
@if (gym.url) { - + } @else { - + }
{{ gym.name ?? gym.id }} diff --git a/CHANGELOG.md b/CHANGELOG.md index df7654c9..7078897b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Containers reported `unhealthy` while serving traffic normally** ([#239](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/239)): the Compose healthcheck probes the app with `curl -sf http://localhost:8080/`, but the `mcr.microsoft.com/dotnet/aspnet:10.0` runtime base (Ubuntu 24.04) ships neither `curl` nor `wget`, so every probe failed with `/bin/sh: 1: curl: not found` and Docker flipped the container to `unhealthy` after three tries. ASP.NET Core was live the whole time, so the impact was cosmetic on a single host — but Swarm, Kubernetes, and auto-healer scripts treat `unhealthy` as a restart/evict signal, so it would misfire in any real deployment. The runtime stage now installs `curl` (`--no-install-recommends`, apt lists removed, ~6.5 MB) before dropping to the `appuser` account. Verified in a built image: `curl 8.5.0` resolves and runs as `appuser`. - **Role gating required *every* listed role, and quoted values locked everyone out** ([#367](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/367)): `allowed_role_ids` is described everywhere as an allow-list of Discord roles that grant access, but `CheckRoleAccessAsync` compared with `HashSet.IsSubsetOf`, which only returned true when the user held **all** of the listed roles. Configuring `123,456` therefore denied anyone who had just one of them. It now matches on `Overlaps` — holding any one listed role is enough. Two smaller problems around the same setting are fixed with it. The setting's tooltip rendered its example wrapped in quotes (`"123456789,987654321"`), so admins pasted the quotes in and the comma-split produced entries like `"123456789` that can never equal a Discord role ID; every non-admin was then denied with nothing but an info-level log to explain it (admins bypass the role check, which is why the site looked admin-only). Values are now parsed with surrounding quotes stripped — straight, curly, guillemet, and low-9 variants, matching the quote styles used across the translated tooltips — and entries that aren't numeric snowflakes are dropped and logged as a warning instead of being kept as unmatchable garbage. If a non-empty setting yields no usable IDs at all, non-admin logins are refused with `role_check_failed` and an error log rather than silently falling open to "allow everyone". Finally the tooltip copy (all 11 locales) and the settings/SSO docs now drop the misleading quotes and state the any-of semantics outright. Both callers are affected — the Discord OAuth callback and the OIDC/external-SSO path share this check. Unit tests cover the parser (quote styles, whitespace, non-snowflake entries, dedup, empty values) and the any-of grant/deny decision. +### Security +- **Gym-picker images no longer send a `Referer` header to third-party hosts** ([#242](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/242)): the gym picker renders two kinds of remote image — the scanner DB's `gym.url` photo (a Niantic CDN URL in stock Golbat/RDM deployments, though an operator can rewrite the column to point at a self-hosted mirror) and the team-icon fallback from `raw.githubusercontent.com`. Neither carried a referrer policy, so every image request told the remote host which PoracleWeb instance the user was browsing. All four `` tags in `gym-picker.component.html` now set `referrerpolicy="no-referrer"`. Modern browsers already default to `strict-origin-when-cross-origin`, so the pre-existing leak was the origin rather than the full URL — this closes the remainder. Presentation-only: no API, model, or scanner-query change, and `GymSearchResult.Url` still carries the raw scanner URL as before. The photo-proxy endpoint floated in the original issue was **not** implemented: server-side fetching of a URL supplied by a database PoracleWeb does not own would turn a passive disclosure into an authenticated outbound-request primitive from a host that can reach Poracle, Koji, Golbat, and both MySQL servers. A host allowlist applied at projection remains the cheaper option if a deployment ever needs the mirror case handled. + ## [2.11.1] - 2026-06-05 ### Fixed