feat: geo-restriction via Cloudflare WAF custom rules
Add country-based access control managed through the Settings page.
Global allow-list with per-app overrides, searchable country selector,
automatic sync to Cloudflare WAF on settings change / deploy / remove,
plus periodic 6-hour verification.
New package: internal/cloudflare/ (client, zone, waf, countries, geosync)
New API: /api/geo/* (6 endpoints) + /api/stacks/{name}/geo/override
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -179,5 +179,141 @@ async function saveOptionalConfig(stackName) {
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{if .GeoGlobalEnabled}}
|
||||
<div class="app-optional-config">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
<p class="config-group-desc">
|
||||
Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi.
|
||||
</p>
|
||||
|
||||
<label class="toggle" style="margin-bottom:1rem">
|
||||
<input type="checkbox" id="app-geo-override-toggle"
|
||||
{{if .GeoAppOverride}}checked{{end}}
|
||||
onchange="toggleAppGeoOverride(this.checked)">
|
||||
<span class="toggle-label">Egyéni országkorlátozás</span>
|
||||
</label>
|
||||
|
||||
<div id="app-geo-override-details" {{if not .GeoAppOverride}}style="display:none"{{end}}>
|
||||
<div class="form-group">
|
||||
<label>Engedélyezett országok ehhez az alkalmazáshoz</label>
|
||||
<div class="geo-country-selector">
|
||||
<input type="text" id="app-geo-search" class="form-control config-input"
|
||||
placeholder="Ország keresése..." autocomplete="off"
|
||||
oninput="filterAppGeoCountries(this.value)"
|
||||
onfocus="showAppGeoList()"
|
||||
onblur="setTimeout(function(){hideAppGeoList()},200)">
|
||||
<div class="geo-country-list" id="app-geo-country-list"></div>
|
||||
</div>
|
||||
<div class="geo-selected-tags" id="app-geo-tags"></div>
|
||||
</div>
|
||||
<div class="config-actions">
|
||||
<button class="btn btn-primary" onclick="saveAppGeoOverride()">Mentés</button>
|
||||
<span id="app-geo-status" class="config-save-status"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var allCountries = [];
|
||||
var appGeoCountries = {{json .GeoAppOverrideCountries}};
|
||||
var stackName = '{{.Stack.Name}}';
|
||||
|
||||
function loadCountries(cb) {
|
||||
if (allCountries.length > 0) { cb(); return; }
|
||||
fetch('/api/geo/countries', {headers: csrfHeaders()})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){ if(d.ok) allCountries = d.data; cb(); })
|
||||
.catch(function(){ cb(); });
|
||||
}
|
||||
|
||||
window.toggleAppGeoOverride = function(enabled) {
|
||||
document.getElementById('app-geo-override-details').style.display = enabled ? '' : 'none';
|
||||
if (enabled) {
|
||||
if (!appGeoCountries || appGeoCountries.length === 0) {
|
||||
appGeoCountries = {{json .GeoGlobalCountries}};
|
||||
}
|
||||
loadCountries(renderAppGeoTags);
|
||||
} else {
|
||||
// Remove override
|
||||
fetch('/api/stacks/' + stackName + '/geo/override', {method:'DELETE', headers:csrfHeaders()});
|
||||
}
|
||||
};
|
||||
|
||||
window.showAppGeoList = function() { loadCountries(function(){ filterAppGeoCountries(''); }); };
|
||||
window.hideAppGeoList = function() { document.getElementById('app-geo-country-list').style.display='none'; };
|
||||
|
||||
window.filterAppGeoCountries = function(q) {
|
||||
var list = document.getElementById('app-geo-country-list');
|
||||
q = q.toLowerCase();
|
||||
var html = ''; var count = 0;
|
||||
for (var i = 0; i < allCountries.length && count < 15; i++) {
|
||||
var c = allCountries[i];
|
||||
if (appGeoCountries.indexOf(c.code) >= 0) continue;
|
||||
if (q && c.name.toLowerCase().indexOf(q) < 0 && c.code.toLowerCase().indexOf(q) < 0) continue;
|
||||
html += '<div class="geo-country-option" onmousedown="addAppGeoCountry(\'' + c.code + '\')">'
|
||||
+ esc(c.name) + ' <small>(' + c.code + ')</small></div>';
|
||||
count++;
|
||||
}
|
||||
list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">Nincs találat</div>';
|
||||
list.style.display = (count > 0 || q) ? '' : 'none';
|
||||
};
|
||||
|
||||
window.addAppGeoCountry = function(code) {
|
||||
if (appGeoCountries.indexOf(code) >= 0) return;
|
||||
appGeoCountries.push(code);
|
||||
renderAppGeoTags();
|
||||
document.getElementById('app-geo-search').value = '';
|
||||
hideAppGeoList();
|
||||
};
|
||||
|
||||
window.removeAppGeoCountry = function(code) {
|
||||
appGeoCountries = appGeoCountries.filter(function(c){return c !== code});
|
||||
renderAppGeoTags();
|
||||
};
|
||||
|
||||
function renderAppGeoTags() {
|
||||
var el = document.getElementById('app-geo-tags');
|
||||
var html = '';
|
||||
for (var i = 0; i < appGeoCountries.length; i++) {
|
||||
var code = appGeoCountries[i];
|
||||
var name = code;
|
||||
for (var j = 0; j < allCountries.length; j++) {
|
||||
if (allCountries[j].code === code) { name = allCountries[j].name; break; }
|
||||
}
|
||||
html += '<span class="geo-tag">' + esc(name) + ' (' + code + ') '
|
||||
+ '<span class="geo-tag-remove" onclick="removeAppGeoCountry(\'' + code + '\')">×</span></span>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
window.saveAppGeoOverride = function() {
|
||||
var status = document.getElementById('app-geo-status');
|
||||
fetch('/api/stacks/' + stackName + '/geo/override', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({allowed_countries: appGeoCountries})
|
||||
})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
status.textContent = d.ok ? (d.message || 'Mentve') : (d.error || 'Hiba');
|
||||
status.className = 'config-save-status ' + (d.ok ? 'config-save-ok' : 'config-save-err');
|
||||
setTimeout(function(){ status.textContent=''; }, 5000);
|
||||
})
|
||||
.catch(function(){
|
||||
status.textContent = 'Hálózati hiba';
|
||||
status.className = 'config-save-status config-save-err';
|
||||
});
|
||||
};
|
||||
|
||||
function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
||||
|
||||
if (document.getElementById('app-geo-override-toggle') && document.getElementById('app-geo-override-toggle').checked) {
|
||||
loadCountries(renderAppGeoTags);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user