fix(analytics): warn about the CSP allowlist on every tracker provider
A self-hosted Umami/Rybbit domain configured in Settings -> Analytics is always blocked by the static script-src allowlist, silently, with only a console error. The amber CSP warning that explains this already existed but was rendered only inside the "custom" provider panel -- not on the two providers where an admin actually types a self-hosted URL. Extract it to a local CspWarning and render it in the Umami and Rybbit panels too. Both translation keys already exist in en.json/de.json. Interpretation: the dynamic-CSP option was investigated and rejected as not reachable for the header that actually governs these documents. In the Docker deployment nginx.conf:58 does `proxy_hide_header Content-Security-Policy`, so helmet's CSP and the res.setHeader CSP at server.js:445 are stripped before they leave the stack -- nginx's static server-level CSP is the only one the browser sees for the SPA documents the tracker is injected into. nginx.conf is COPYied verbatim by the Dockerfile (only index.html goes through envsubst), and the tracker URL lives in the DB rather than the environment, so making it reflect the setting would need start-time templating plus a DB read. The CSP itself therefore still has to be edited by hand; the warning now says so where the admin can see it. Refs testplan REPORT.md #18 (Part 3, B.02).
This commit is contained in:
@@ -15,6 +15,35 @@ interface AnalyticsTabProps {
|
|||||||
|
|
||||||
const PROVIDER_OPTIONS: TrackerProvider[] = ['none', 'umami', 'rybbit', 'custom'];
|
const PROVIDER_OPTIONS: TrackerProvider[] = ['none', 'umami', 'rybbit', 'custom'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shipped CSP `script-src` is a static allowlist that no configured
|
||||||
|
* tracker domain is ever added to, so a self-hosted Umami/Rybbit instance is
|
||||||
|
* blocked by the browser with nothing but a console error to show for it.
|
||||||
|
* Shown for every provider that loads a script from another origin.
|
||||||
|
*/
|
||||||
|
const CspWarning: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<AlertCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0" />
|
||||||
|
<div className="text-sm text-amber-800 dark:text-amber-200">
|
||||||
|
<p className="font-medium mb-1">
|
||||||
|
{t('settings.analytics.customCspWarning', 'Content-Security-Policy reminder')}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
'settings.analytics.customCspWarningText',
|
||||||
|
'PicPeak ships with a strict CSP (`script-src \'self\'`). If your tracker loads from another domain, add that domain to your reverse-proxy or nginx CSP config — otherwise the browser silently blocks the script.',
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({
|
export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({
|
||||||
analyticsSettings,
|
analyticsSettings,
|
||||||
setAnalyticsSettings,
|
setAnalyticsSettings,
|
||||||
@@ -128,6 +157,8 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CspWarning />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -191,6 +222,8 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CspWarning />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -222,22 +255,7 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
<CspWarning />
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<AlertCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0" />
|
|
||||||
<div className="text-sm text-amber-800 dark:text-amber-200">
|
|
||||||
<p className="font-medium mb-1">
|
|
||||||
{t('settings.analytics.customCspWarning', 'Content-Security-Policy reminder')}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{t(
|
|
||||||
'settings.analytics.customCspWarningText',
|
|
||||||
'PicPeak ships with a strict CSP (`script-src \'self\'`). If your tracker loads from another domain, add that domain to your reverse-proxy or nginx CSP config — otherwise the browser silently blocks the script.',
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user