qr -> ajax

This commit is contained in:
Simon Martens
2025-05-30 03:01:38 +02:00
parent 0f22f14a56
commit 9f7306526f
3 changed files with 89 additions and 109 deletions

View File

@@ -74,12 +74,8 @@ func (p *UserManagementAccessPage) GET(engine *templating.Engine, app core.App)
data["relative_url"] = path_access + "?token=" + access_token.Token() data["relative_url"] = path_access + "?token=" + access_token.Token()
data["validUntil"] = access_token.Expires().Time().Local().Format("02.01.2006 15:04") data["validUntil"] = access_token.Expires().Time().Local().Format("02.01.2006 15:04")
nonce, token, err := CSRF_CACHE.GenerateTokenBundle() req := templating.NewRequest(e)
if err != nil { data["csrf_token"] = req.Session().Token
return engine.Response500(e, err, data)
}
data["csrf_nonce"] = nonce
data["csrf_token"] = token
SetRedirect(data, e) SetRedirect(data, e)
@@ -94,6 +90,19 @@ func (p *UserManagementAccessPage) POST(engine *templating.Engine, app core.App)
return engine.Response404(e, fmt.Errorf("invalid role: %s", role), nil) return engine.Response404(e, fmt.Errorf("invalid role: %s", role), nil)
} }
formdata := struct {
CSRF string `json:"csrf_token" form:"csrf_token"`
}{}
if err := e.BindBody(&formdata); err != nil {
return engine.Response500(e, fmt.Errorf("invalid form data: %w", err), nil)
}
req := templating.NewRequest(e)
if err := req.CheckCSRF(formdata.CSRF); err != nil {
return engine.Response500(e, fmt.Errorf("invalid CSRF token: %w", err), nil)
}
path_access := URL_USER_CREATE + role path_access := URL_USER_CREATE + role
record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_URL_FIELD, path_access) record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_URL_FIELD, path_access)
if err == nil { if err == nil {
@@ -110,6 +119,7 @@ func (p *UserManagementAccessPage) POST(engine *templating.Engine, app core.App)
data["access_url"] = "https://musenalm.de" + path_access + "?token=" + token.Token() data["access_url"] = "https://musenalm.de" + path_access + "?token=" + token.Token()
data["relative_url"] = path_access + "?token=" + token.Token() data["relative_url"] = path_access + "?token=" + token.Token()
data["validUntil"] = token.Expires().Time().Format("02.01.2006 15:04") data["validUntil"] = token.Expires().Time().Format("02.01.2006 15:04")
data["csrf_token"] = req.Session().Token
SetRedirect(data, e) SetRedirect(data, e)

File diff suppressed because one or more lines are too long

View File

@@ -4,10 +4,6 @@
<script src="/assets/js/qrcode.min.js"></script> <script src="/assets/js/qrcode.min.js"></script>
<script type="module"> <script type="module">
const qrElement = document.getElementById('qr');
const tokenElement = document.getElementById('token');
const accessUrl = "{{ $model.access_url }}";
/** /**
* @param {number} timeout - Maximum time to wait in milliseconds. * @param {number} timeout - Maximum time to wait in milliseconds.
* @param {number} interval - How often to check in milliseconds. * @param {number} interval - How often to check in milliseconds.
@@ -17,15 +13,15 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let elapsedTime = 0; let elapsedTime = 0;
const checkInterval = setInterval(() => { const checkInterval = setInterval(() => {
if (typeof window.QRCode === 'function') { if (typeof window.QRCode === "function") {
clearInterval(checkInterval); clearInterval(checkInterval);
resolve(window.QRCode); // Resolve with the QRCode object/function resolve(window.QRCode); // Resolve with the QRCode object/function
} else { } else {
elapsedTime += interval; elapsedTime += interval;
if (elapsedTime >= timeout) { if (elapsedTime >= timeout) {
clearInterval(checkInterval); clearInterval(checkInterval);
console.error('Timed out waiting for QRCode to become available.'); console.error("Timed out waiting for QRCode to become available.");
reject(new Error('QRCode not available after ' + timeout + 'ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode.')); reject(new Error("QRCode not available after " + timeout + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode."));
} }
} }
}, interval); }, interval);
@@ -34,57 +30,60 @@
// INFO: We have to wait for the QRCode object to be available. It's messy. // INFO: We have to wait for the QRCode object to be available. It's messy.
async function genQRCode() { async function genQRCode() {
console.debug("Generating QR Code...");
const QRCode = await getQRCodeWhenAvailable(); const QRCode = await getQRCodeWhenAvailable();
if (qrElement && accessUrl && qrElement.innerHTML.trim() === '') { const tokenElement = document.getElementById("token");
const qrElement = document.getElementById("qr");
if (qrElement) {
// INFO: Clear previous QR code if any
// Also hide it initially to prevent flickering
qrElement.innerHTML = "";
qrElement.classList.add("hidden");
new QRCode(qrElement, { new QRCode(qrElement, {
text: accessUrl, text: tokenElement.value,
width: 1280, width: 1280,
height: 1280, height: 1280,
colorDark: "#000000", colorDark: "#000000",
colorLight: "#ffffff", colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H correctLevel: QRCode.CorrectLevel.H,
}); });
setTimeout(() => { setTimeout(() => {
qrElement.classList.remove('hidden'); qrElement.classList.remove("hidden");
}, 20); }, 20);
} }
// Add event listeners to the token input field to select its content on focus or click
if (tokenElement) {
tokenElement.addEventListener("focus", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mousedown", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mouseup", (ev) => {
ev.preventDefault();
tokenElement.select();
});
}
} }
genQRCode(); genQRCode();
if (tokenElement) { window.genQRCode = genQRCode;
tokenElement.addEventListener('focus', (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener('mousedown', (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener('mouseup', (ev) => {
ev.preventDefault();
tokenElement.select();
});
}
</script> </script>
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8"> <div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
<div class="flex-col w-full"> <div class="flex-col w-full">
{{ if $model.redirect_url }} {{ if $model.redirect_url }}
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950"> <a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Zurück </a>
<i class="ri-arrow-left-s-line"></i> Zurück
</a>
{{ else }} {{ else }}
<a href="/" class="text-gray-700 hover:text-slate-950"> <a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
<i class="ri-arrow-left-s-line"></i> Startseite
</a>
{{ end }} {{ end }}
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900"> <h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">Nutzer einladen</h1>
Nutzer einladen
</h1>
<div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[60ch]"> <div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[60ch]">
<i class="ri-question-line"></i> <i class="ri-question-line"></i>
Mit diesem Link können neue Accounts registriert werden. Geben Sie den Link an Personen Mit diesem Link können neue Accounts registriert werden. Geben Sie den Link an Personen weiter, die Sie einladen möchten.
weiter, die Sie einladen möchten.
</div> </div>
</div> </div>
</div> </div>
@@ -93,10 +92,12 @@
<div class="flex flex-row w-full"> <div class="flex flex-row w-full">
<div class="flex-col max-w-xl w-full"> <div class="flex-col max-w-xl w-full">
<!-- QR Code --> <!-- QR Code -->
<div class="mb-4 p-4 border rounded-xs hidden" id="qr"></div> <div class="mb-4 p-4 border rounded-xs min-w-full aspect-square">
<div id="qr"></div>
</div>
<!-- Access URL and Token --> <!-- Access URL and Token -->
<div class="mb-6 flex flex-col"> <div class="mb-6 flex flex-col" id="token-container">
<div class="flex flex-row"> <div class="flex flex-row">
<input <input
type="text" type="text"
@@ -116,13 +117,14 @@
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
focus:outline-none no-underline focus:outline-none no-underline
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500" focus:ring-2 focus:ring-offset-2 focus:ring-slate-500"
onclick="navigator.clipboard.writeText('{{ $model.access_url }}')"> onclick="navigator.clipboard.writeText(token.value)">
<i class="ri-file-copy-line"></i> <i class="ri-file-copy-line"></i>
</button> </button>
</tool-tip> </tool-tip>
<tool-tip position="top"> <tool-tip position="top">
<div class="data-tip font-bold hidden">In neuem Fenster öffnen</div> <div class="data-tip font-bold hidden">In neuem Fenster öffnen</div>
<a <a
id="access-link"
href="{{ $model.relative_url }}" href="{{ $model.relative_url }}"
target="_blank" target="_blank"
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
@@ -133,19 +135,8 @@
<i class="ri-external-link-line"></i> <i class="ri-external-link-line"></i>
</a> </a>
</tool-tip> </tool-tip>
<form class="" method="POST"> <form class="" method="POST" hx-boost="false" x-target="token access-link" @ajax:after="genQRCode()">
<input <input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
type="hidden"
name="csrf_nonce"
id="csrf_nonce"
required
value="{{ $model.csrf_nonce }}" />
<input
type="hidden"
name="csrf_token"
id="csrf_token"
required
value="{{ $model.csrf_token }}" />
<div class="col-span-9 flex flex-row items-center justify-center"> <div class="col-span-9 flex flex-row items-center justify-center">
<tool-tip position="top"> <tool-tip position="top">
<div class="data-tip font-bold hidden">Link invalidieren</div> <div class="data-tip font-bold hidden">Link invalidieren</div>
@@ -175,52 +166,31 @@
<div class="ml-12 flex flex-col"> <div class="ml-12 flex flex-col">
<!-- User Role Chooser --> <!-- User Role Chooser -->
<div class="user-chooser flex flex-col gap-y-3"> <div class="user-chooser flex flex-col gap-y-3">
<a <a href="/user/management/access/User?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "User" }}aria-current="page"{{ end }}> Benutzer </a>
href="/user/management/access/User?redirectTo={{ $model.redirect_url }}"
{{ if eq $model.role "User" }}aria-current="page"{{ end }}>
Benutzer
</a>
{{ if eq $model.role "User" }} {{ if eq $model.role "User" }}
<div class="max-w-[60ch] text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto"> <div class="max-w-[60ch] text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto">
<div> <div>
<i class="ri-error-warning-line"></i> <i class="ri-error-warning-line"></i>
</div> </div>
<div> <div>Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder löschen.</div>
Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder
löschen.
</div>
</div> </div>
{{- end -}} {{- end -}}
<a <a href="/user/management/access/Editor?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "Editor" }}aria-current="page"{{ end }}> Redakteur </a>
href="/user/management/access/Editor?redirectTo={{ $model.redirect_url }}"
{{ if eq $model.role "Editor" }}aria-current="page"{{ end }}>
Redakteur
</a>
{{- if eq $model.role "Editor" -}} {{- if eq $model.role "Editor" -}}
<div class="max-w-[60ch] text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto"> <div class="max-w-[60ch] text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto">
<div> <div>
<i class="ri-error-warning-line"></i> <i class="ri-error-warning-line"></i>
</div> </div>
<div> <div>Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten oder löschen.</div>
Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten oder
löschen.
</div>
</div> </div>
{{- end -}} {{- end -}}
<a <a href="/user/management/access/Admin?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "Admin" }}aria-current="page"{{ end }}> Admin </a>
href="/user/management/access/Admin?redirectTo={{ $model.redirect_url }}"
{{ if eq $model.role "Admin" }}aria-current="page"{{ end }}>
Admin
</a>
{{- if eq $model.role "Admin" -}} {{- if eq $model.role "Admin" -}}
<div class="max-w-[60ch] text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto"> <div class="max-w-[60ch] text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto">
<div> <div>
<i class="ri-error-warning-line"></i> <i class="ri-error-warning-line"></i>
</div> </div>
<div> <div>Administratoren können alle Felder und Datensätze einsehen, bearbeiten oder löschen. Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.</div>
Administratoren können alle Felder und Datensätze einsehen, bearbeiten oder löschen.
Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.
</div>
</div> </div>
{{- end -}} {{- end -}}
</div> </div>