Files
GuacPanel/webgui/templates/basicauth.html
T
njomac 1a213f807f feat: Basic Auth se moze ukljuciti/iskljuciti iz Web GUI-ja
Panel je dosad znao samo dodavati i brisati htpasswd korisnike, a sama
auth_basic direktiva se mogla dirati jedino rucno u nginx konfiguraciji.

Dodan prekidac po lokaciji — /guacamole/ i /guacadmin/ su odvojeni, pa
se zastita moze skinuti sa samo jedne. Stanje se cita iz konfiguracije,
ne pamti se zasebno, pa GUI uvijek pokazuje ono sto nginx stvarno radi.

Sigurnosne kocnice:
- Nakon izmjene ide nginx -t; ako padne, konfiguracija se vraca na staro
  prije nego se reload uopce dogodi.
- Ne moze se ukljuciti zastita ako nema nijednog htpasswd korisnika —
  to bi zakljucalo sve.
- Ne moze se obrisati zadnji korisnik dok je Basic Auth aktivan. Ta rupa
  je postojala i prije: brisanje zadnjeg korisnika ostavljalo je prazan
  htpasswd uz aktivan auth_basic, sto zakljuca i Guacamole i sam panel.

Parsiranje ide po location bloku s brojanjem viticastih zagrada, ne
regexom preko cijele datoteke, da se ne pogodi kriva lokacija.

Provjereno na zivoj instalaciji: iskljucenje na /guacamole/ daje 200 bez
prijave dok /guacadmin/ ostaje na 401; ponovno ukljucenje vraca 401 i
konfiguracija je bajt-identicna originalu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 10:08:26 +02:00

172 lines
5.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base.html' %}
{% block title %}Basic Auth Guacamole Manager{% endblock %}
{% block content %}
<h4 class="mb-4"><i class="bi bi-person-lock me-2"></i>Basic Auth korisnici</h4>
<div class="card mb-4">
<div class="card-header fw-semibold">
<i class="bi bi-shield-lock me-1"></i> Basic Auth zaštita
</div>
<div class="card-body">
{% for loc, label in auth_locations.items() %}
<div class="d-flex justify-content-between align-items-center
{% if not loop.last %}mb-3 pb-3 border-bottom{% endif %}"
style="border-color:#30363d !important">
<div>
<div class="fw-semibold">{{ label }}</div>
<small class="text-muted"><code>{{ loc }}</code></small>
</div>
<div class="form-check form-switch mb-0">
{% if auth_state[loc] is none %}
<span class="badge bg-secondary">nije konfiguriran</span>
{% else %}
<input class="form-check-input" type="checkbox" role="switch"
id="sw-{{ loop.index }}" {% if auth_state[loc] %}checked{% endif %}
onchange="toggleAuth('{{ loc }}', this)">
{% endif %}
</div>
</div>
{% endfor %}
<div class="alert alert-warning mt-3 mb-0 py-2 small">
<i class="bi bi-exclamation-triangle me-1"></i>
Isključite li zaštitu, na toj lokaciji nestaje prvi sloj obrane i
<strong>fail2ban jail <code>nginx-basicauth</code> ostaje bez posla</strong>.
Za <code>/guacadmin/</code> to znači da je ovaj panel izložen samo
s vlastitom prijavom.
</div>
</div>
</div>
<div class="row g-4">
<!-- Lista korisnika -->
<div class="col-md-5">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-semibold">Korisnici</span>
<span class="badge bg-secondary">{{ users|length }}</span>
</div>
<ul class="list-group list-group-flush" id="user-list">
{% for user in users %}
<li class="list-group-item d-flex justify-content-between align-items-center"
style="background:transparent; border-color:#30363d">
<span><i class="bi bi-person me-2 text-muted"></i>{{ user }}</span>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary btn-sm"
onclick="fillUpdate('{{ user }}')">
<i class="bi bi-key"></i>
</button>
<button class="btn btn-outline-danger btn-sm"
onclick="deleteUser('{{ user }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</li>
{% else %}
<li class="list-group-item text-muted" style="background:transparent">
Nema korisnika
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- Forme -->
<div class="col-md-7">
<!-- Dodaj korisnika -->
<div class="card mb-3">
<div class="card-header fw-semibold">
<i class="bi bi-person-plus me-1"></i> Dodaj korisnika
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-5">
<input id="add-user" type="text" class="form-control form-control-sm"
placeholder="Korisničko ime">
</div>
<div class="col-5">
<input id="add-pass" type="password" class="form-control form-control-sm"
placeholder="Lozinka">
</div>
<div class="col-2">
<button class="btn btn-primary btn-sm w-100" onclick="addUser()">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Promijeni lozinku -->
<div class="card">
<div class="card-header fw-semibold">
<i class="bi bi-key me-1"></i> Promijeni lozinku
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-5">
<input id="upd-user" type="text" class="form-control form-control-sm"
placeholder="Korisničko ime">
</div>
<div class="col-5">
<input id="upd-pass" type="password" class="form-control form-control-sm"
placeholder="Nova lozinka">
</div>
<div class="col-2">
<button class="btn btn-outline-primary btn-sm w-100" onclick="updateUser()">
<i class="bi bi-check-lg"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function fillUpdate(user) {
document.getElementById('upd-user').value = user;
document.getElementById('upd-pass').focus();
}
async function addUser() {
const username = document.getElementById('add-user').value.trim();
const password = document.getElementById('add-pass').value;
const r = await api('/api/basicauth/add', {username, password});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
async function updateUser() {
const username = document.getElementById('upd-user').value.trim();
const password = document.getElementById('upd-pass').value;
const r = await api('/api/basicauth/update', {username, password});
showToast(r.msg, r.ok);
}
async function toggleAuth(location, el) {
const enabled = el.checked;
if (!enabled && !confirm(
`Isključiti Basic Auth za ${location}?\n\n` +
`Nakon toga se toj lokaciji pristupa bez prompta.`)) {
el.checked = true;
return;
}
el.disabled = true;
const r = await api('/api/basicauth/toggle', {location, enabled});
showToast(r.msg, r.ok);
if (!r.ok) el.checked = !enabled; // odbijeno — vrati prekidac
el.disabled = false;
}
async function deleteUser(username) {
if (!confirm(`Ukloniti korisnika "${username}"?`)) return;
const r = await api('/api/basicauth/delete', {username});
showToast(r.msg, r.ok);
if (r.ok) setTimeout(() => location.reload(), 1000);
}
</script>
{% endblock %}