feat(captcha): remove Turnstile head-script injection (cms Cap captcha Phase E)
Cap is the sole captcha provider in the CMS; the Cloudflare Turnstile TurnstileSiteKey field, settings read, and turnstileScript CDN injector are retired from the shared bn head template.
This commit is contained in:
parent
a3b261dbe4
commit
7a82028618
@ -51,7 +51,6 @@ type SiteSettingsData struct {
|
||||
AdminBypassMode string // "maintenance", "coming_soon", or "" if not bypassing
|
||||
Toolbar ToolbarData
|
||||
LLMsTxtEnabled bool // Whether llms.txt is enabled for AI content discovery
|
||||
TurnstileSiteKey string // Cloudflare Turnstile site key for bot protection
|
||||
RSSFeedURL string // URL to the RSS feed (e.g., "/rss"), empty to disable
|
||||
RSSFeedTitle string // Feed title for discovery link
|
||||
}
|
||||
@ -229,16 +228,6 @@ func ParseSiteSettings(doc map[string]any) SiteSettingsData {
|
||||
}
|
||||
}
|
||||
|
||||
// Turnstile bot protection (from site settings)
|
||||
if turnstileData, ok := siteData["turnstile"].(map[string]any); ok {
|
||||
// Only set site key if Turnstile is enabled
|
||||
if enabled, ok := turnstileData["enabled"].(bool); ok && enabled {
|
||||
if v, ok := turnstileData["site_key"].(string); ok {
|
||||
settings.TurnstileSiteKey = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RSS feed auto-discovery (injected by page handler from system page)
|
||||
if v, ok := siteData["rss_feed_url"].(string); ok {
|
||||
settings.RSSFeedURL = v
|
||||
@ -541,10 +530,6 @@ templ Head(data HeadData) {
|
||||
if data.Settings.CustomHeadScripts != "" {
|
||||
@templ.Raw(data.Settings.CustomHeadScripts)
|
||||
}
|
||||
// Cloudflare Turnstile invisible bot protection
|
||||
if data.Settings.TurnstileSiteKey != "" {
|
||||
@turnstileScript(data.Settings.TurnstileSiteKey)
|
||||
}
|
||||
if data.StructuredData != "" {
|
||||
@structuredDataScript(data.StructuredData)
|
||||
}
|
||||
@ -557,117 +542,6 @@ func structuredDataScript(jsonLD string) templ.Component {
|
||||
return templ.Raw(`<script type="application/ld+json">` + jsonLD + `</script>`)
|
||||
}
|
||||
|
||||
// turnstileScript renders the Cloudflare Turnstile invisible bot protection
|
||||
templ turnstileScript(siteKey string) {
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
||||
<script data-turnstile-key={ siteKey }>
|
||||
(function(){
|
||||
var siteKey = document.currentScript.getAttribute('data-turnstile-key');
|
||||
if (!siteKey) return;
|
||||
|
||||
// Store active widget IDs per form
|
||||
var formWidgets = new WeakMap();
|
||||
|
||||
// Initialize Turnstile when API is ready
|
||||
function initTurnstile() {
|
||||
if (typeof turnstile === 'undefined') {
|
||||
setTimeout(initTurnstile, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all forms that POST to /api/* endpoints
|
||||
function instrumentForm(form) {
|
||||
// Skip if already instrumented
|
||||
if (formWidgets.has(form)) return;
|
||||
|
||||
var action = form.action || '';
|
||||
var hxPost = form.getAttribute('hx-post') || '';
|
||||
|
||||
// Only protect /api/* POST endpoints
|
||||
if (!action.includes('/api/') && !hxPost.includes('/api/')) return;
|
||||
|
||||
// Skip exempted endpoints
|
||||
var exempted = ['/api/track', '/api/webhooks/'];
|
||||
for (var i = 0; i < exempted.length; i++) {
|
||||
if (action.includes(exempted[i]) || hxPost.includes(exempted[i])) return;
|
||||
}
|
||||
|
||||
// Create container for widget
|
||||
var container = document.createElement('div');
|
||||
container.className = 'cf-turnstile-container';
|
||||
container.style.cssText = 'position:absolute;left:-9999px;';
|
||||
form.appendChild(container);
|
||||
|
||||
// Render invisible widget
|
||||
var widgetId = turnstile.render(container, {
|
||||
sitekey: siteKey,
|
||||
size: 'invisible',
|
||||
callback: function(token) {
|
||||
// Store token in hidden field
|
||||
var input = form.querySelector('input[name="cf-turnstile-response"]');
|
||||
if (!input) {
|
||||
input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'cf-turnstile-response';
|
||||
form.appendChild(input);
|
||||
}
|
||||
input.value = token;
|
||||
}
|
||||
});
|
||||
|
||||
formWidgets.set(form, widgetId);
|
||||
}
|
||||
|
||||
// Instrument existing forms
|
||||
document.querySelectorAll('form').forEach(instrumentForm);
|
||||
|
||||
// Watch for new forms (dynamic content / HTMX)
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if (node.nodeName === 'FORM') {
|
||||
instrumentForm(node);
|
||||
}
|
||||
if (node.querySelectorAll) {
|
||||
node.querySelectorAll('form').forEach(instrumentForm);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// HTMX integration: add token to request parameters
|
||||
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||
var form = evt.detail.elt.closest('form');
|
||||
if (!form || !formWidgets.has(form)) return;
|
||||
|
||||
var widgetId = formWidgets.get(form);
|
||||
var token = turnstile.getResponse(widgetId);
|
||||
|
||||
if (token) {
|
||||
evt.detail.parameters['cf-turnstile-response'] = token;
|
||||
}
|
||||
});
|
||||
|
||||
// Reset widgets after HTMX swap
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.querySelectorAll) {
|
||||
evt.detail.target.querySelectorAll('form').forEach(instrumentForm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start initialization
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initTurnstile);
|
||||
} else {
|
||||
initTurnstile();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
// AdminBypassBanner renders a banner when admin is bypassing maintenance/coming_soon mode
|
||||
// This should be rendered at the very start of the <body> to push down all content
|
||||
templ AdminBypassBanner(settings SiteSettingsData) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user